components: schemas: AIDraftingStruct: additionalProperties: false properties: enabled: description: True when the server is started with SPARROW_AI_ENABLED=true, whether or not the provider settings are valid. Drafting (POST /v1/subscriptions:draftTemplate) is usable only when enabled is true and problem is absent. type: boolean model: description: Model id used for drafting; absent when drafting is off or not usable. type: string problem: description: "Why drafting is enabled but not usable: a missing or invalid provider setting. POST /v1/subscriptions:draftTemplate answers 503 with this text." type: string prompt_only: description: "Always the opposite of enabled: true when SPARROW_AI_ENABLED is not true, so clients offer only the copy-and-paste prompt. POST /v1/subscriptions:draftTemplatePrompt works whatever this says." type: boolean provider: description: "Chat API behind drafting: anthropic, or openai for any OpenAI-compatible server (Ollama, vLLM, OpenRouter, OpenAI). Absent when drafting is off or not usable." enum: - anthropic - openai type: string required: - enabled - prompt_only type: object AlertConfigItem: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/AlertConfigItem.json format: uri readOnly: true type: string consumer: description: Consumer this alert config belongs to. type: string created_at: description: Creation timestamp, RFC3339. format: date-time type: string email: description: Recipient email address. type: string event_types: description: Sparrow system event types this recipient is alerted on. items: type: string type: array id: description: Alert config id (UUID). type: string webhook_id: description: Webhook this alert is scoped to; omitted for consumer-wide alerts. type: string required: - id - consumer - email - event_types - created_at type: object AlertDeliveryStruct: additionalProperties: false properties: configured: description: True when an active webhook under the _sparrow consumer has an unpaused subscription to Sparrow's system events, so alert configs actually send email. When false, alert configs are stored but nothing is sent. type: boolean required: - configured type: object AttemptItem: additionalProperties: false properties: error_category: description: Failure classification for this attempt. enum: - success - client_error - server_error - timeout - dns_error - tls_error - connection_refused - network_error - rate_limited - unexpected_status - template_error - unknown type: string error_message: description: Human-readable failure reason for this attempt. type: string response_code: description: HTTP status code returned by the endpoint on this attempt; 0 when no response was received. format: int64 type: integer response_time: description: Round-trip time of this attempt, in milliseconds. format: int64 type: integer success: description: Whether this attempt succeeded (got one of the webhook's expected_status_codes). type: boolean timestamp: description: When this attempt was made, RFC3339. format: date-time type: string required: - success - response_time - response_code - timestamp type: object AttemptsOutputBody: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/AttemptsOutputBody.json format: uri readOnly: true type: string items: description: Every recorded attempt, oldest first. items: $ref: "#/components/schemas/AttemptItem" type: array required: - items type: object BatchJobOutputBody: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/BatchJobOutputBody.json format: uri readOnly: true type: string consumer: description: Consumer the snapshot was taken in; empty for a snapshot taken from a cross-consumer list. type: string created_at: description: Creation timestamp, RFC3339. format: date-time type: string expires_at: description: "When the snapshot expires, RFC3339: 15 minutes after it was taken. An expired snapshot can no longer be started." format: date-time type: string failed: description: Items that could not be re-pushed or retried (e.g. the event or delivery no longer exists). format: int64 type: integer id: description: "Batch job id (UUID): the repush_id or retry_id from the list call." type: string job_type: description: Which batch operation this job performs. enum: - event_repush - delivery_retry type: string processed: description: Items handled successfully so far. format: int64 type: integer status: description: "pending: snapshot taken, not started. processing: running. completed: every item was handled (see failed for the ones that could not be). failed: every item failed. cancelled: stopped by a cancel call." enum: - pending - processing - completed - failed - cancelled type: string total: description: Total items in the batch snapshot. format: int64 type: integer required: - id - consumer - job_type - status - total - processed - failed - created_at - expires_at type: object BundleItem: additionalProperties: false properties: active: description: Whether events of this type can be pushed. Omitted means true. type: boolean description: description: "Human-readable summary. Omitted means empty: an import replaces the definition." type: string event_schema: additionalProperties: {} description: JSON Schema. Omitted means no schema, which removes an existing schema (a breaking change). type: object metadata: additionalProperties: type: string description: Arbitrary key/value metadata. Omitted means none. type: object name: description: Event type name. maxLength: 255 minLength: 1 type: string required: - name type: object BundleStampBody: additionalProperties: false properties: format: description: Bundle format number. format: int64 type: integer sha256: description: sha256 of the canonical encoding of items, to tell whether they changed after export. type: string sparrow_version: description: Version of the Sparrow server that exported the bundle. type: string required: - sparrow_version - format - sha256 type: object CapabilitiesOutputBody: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/CapabilitiesOutputBody.json format: uri readOnly: true type: string ai_drafting: $ref: "#/components/schemas/AIDraftingStruct" description: AI-assisted transform template drafting. alert_delivery: $ref: "#/components/schemas/AlertDeliveryStruct" description: Delivery of Sparrow's own alert emails (see the alert-configs endpoints). portal: $ref: "#/components/schemas/PortalStruct" description: What the consumer portal is allowed to do on this server. required: - ai_drafting - alert_delivery - portal type: object ConsumerHint: additionalProperties: false properties: name: description: Consumer to register the recipe under, e.g. _sparrow. type: string note: description: Why, and what the recipe does under any other consumer. type: string required: - name - note type: object ConsumerItem: additionalProperties: false properties: name: description: Consumer name. type: string required: - name type: object ConsumerStatsOutputBody: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/ConsumerStatsOutputBody.json format: uri readOnly: true type: string active_webhooks: description: Webhooks currently active (not paused). format: int64 type: integer failed_deliveries: description: Attempts that failed, all time, including failures that were later retried successfully. Lags like total_deliveries. format: int64 type: integer pending_deliveries: description: Deliveries waiting to be sent or retried right now (pending, sending or retrying). format: int64 type: integer success_rate: description: successful_deliveries / total_deliveries, 0.0 to 1.0. format: double type: number successful_deliveries: description: Attempts that succeeded, all time. Lags like total_deliveries. format: int64 type: integer total_deliveries: description: Delivery attempts recorded, all time. A retried delivery counts once per attempt. Updated by each health evaluation (SPARROW_HEALTH_EVAL_INTERVAL), so it can lag by that much. format: int64 type: integer total_webhooks: description: Total webhooks registered. format: int64 type: integer required: - total_webhooks - active_webhooks - total_deliveries - successful_deliveries - failed_deliveries - pending_deliveries - success_rate type: object CreateAlertConfigBody: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/CreateAlertConfigBody.json format: uri readOnly: true type: string email: description: Recipient email address. format: email type: string event_types: description: "Sparrow system event types to alert on, at least one of: sparrow.webhook.health_changed, sparrow.webhook.delivery_failed, sparrow.webhook.disabled." items: type: string type: array webhook_id: description: Scope the alert to one webhook. Omit for a consumer-wide alert covering every webhook. type: string required: - email - event_types type: object CreateInviteBody: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/CreateInviteBody.json format: uri readOnly: true type: string consumer: description: "Invite into one consumer's portal: the token it creates is limited to that consumer. Omit for tenant-wide (operator console) access. Same naming rules as on POST /v1/tokens." type: string name: description: Name for the token the invite creates, e.g. the invitee's name. maxLength: 200 minLength: 1 type: string token_never_expires: description: The token it creates never expires; same rules as never_expires on POST /v1/tokens. type: boolean token_ttl_seconds: description: Lifetime in seconds of the token it creates, counted from redemption; same defaults and limits as ttl_seconds on POST /v1/tokens. format: int64 minimum: 0 type: integer ttl_seconds: description: How long the invite can be redeemed, in seconds. 0 or omitted means 24 hours; at most 7 days. format: int64 minimum: 0 type: integer required: - name type: object CreateInviteOutputBody: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/CreateInviteOutputBody.json format: uri readOnly: true type: string invite: $ref: "#/components/schemas/InviteOut" description: The created invite. path: description: "UI-relative link with the secret in the fragment (never sent to servers or logs): /#invite=... for a tenant-wide invite, /portal#invite=... for a consumer invite. Prepend the base URL the UI is served from." type: string secret: description: The invite secret. Shown only in this response. type: string required: - invite - secret - path type: object CreateSubscriptionBody: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/CreateSubscriptionBody.json format: uri readOnly: true type: string event_name: description: Event type name to subscribe to, or "*" to receive every event in the consumer (catch-all). sparrow.* names only under the _sparrow consumer. One subscription per webhook, event name and consumer (409 on a duplicate). type: string headers: additionalProperties: type: string description: Extra HTTP headers sent with deliveries from this subscription. They override the webhook's headers of the same name; the webhook's secret_headers override both. Same name and value rules as webhook headers. type: object label_filters: additionalProperties: type: string description: Key/value pairs that must ALL be present in an event's labels for this subscription to receive it. Empty means match every event of event_name. Same limits as event labels (20 pairs, 64-character keys, 256-character values). type: object method: description: HTTP method used for deliveries from this subscription, e.g. PUT. Defaults to POST. type: string on_transform_error: description: "What happens when transform_template fails to render. fail (default): the delivery fails with error category template_error, is not retried automatically, and can be retried once the template is fixed; it does not affect the webhook's health. fallback: the default envelope payload is sent instead, and the error is still recorded on the delivery." enum: - fail - fallback type: string template_missing_key: description: "How transform_template reads a key the payload does not have. error (default): the render fails, so a field removed from the event schema cannot silently turn into \"\" in the body; read optional fields with index or dig (default only fills a key that is present but empty). zero: the key renders as \"\"." enum: - error - zero type: string timeout: description: Request timeout for this subscription's deliveries, in seconds, overriding the webhook's request_timeout_seconds. 0 to 300; 0 (default) uses the webhook's. format: int64 type: integer transform_enabled: description: Whether to render transform_template into the delivered body instead of sending the default envelope. Defaults to false. type: boolean transform_template: description: Go text/template rendered against the event to produce the delivered body, used when transform_enabled is true. See GET /v1/template-functions for available helpers; test it with POST /v1/subscriptions:testTemplate. A template that does not parse is rejected with 400; it is not rendered on save. Saving a new template records a version in the template history. type: string webhook_id: description: Webhook id (UUID) to deliver matching events to. Must belong to this consumer. type: string required: - webhook_id - event_name type: object CreateTokenBody: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/CreateTokenBody.json format: uri readOnly: true type: string consumer: description: "Limit the token to one consumer: it then works only through the portal API (/portal/api/). Omit for a tenant-wide token with the same power as SPARROW_API_KEY. At most 255 characters, no /, \\, ?, #, % or dot segments; _sparrow is not allowed." type: string external_id: description: "Consumer tokens only. Your id for who or what the token is for, e.g. your user id when embedding the portal. There is at most one active token per consumer and external_id: while it is valid, asking again returns it, secret included (reused: true; name and ttl_seconds are ignored), instead of creating another; once it expires or is revoked, a new one is created. This makes minting a portal link on every page view safe. Not a secret: it is fine to log." maxLength: 200 type: string name: description: Who or what the token is for, e.g. "alice" or "ci-deploy". maxLength: 200 minLength: 1 type: string never_expires: description: Create a tenant-wide token that never expires (revoke it when no longer needed). Not allowed with ttl_seconds or for consumer tokens. type: boolean ttl_seconds: description: "Lifetime in seconds. 0 or omitted takes the default: SPARROW_TOKEN_DEFAULT_TTL (90 days unless changed) for tenant-wide tokens, 7 days for consumer tokens. Consumer tokens allow at most 30 days." format: int64 minimum: 0 type: integer required: - name type: object CreateTokenOutputBody: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/CreateTokenOutputBody.json format: uri readOnly: true type: string portal_path: description: "Consumer tokens only: server-relative portal URL with the token in the fragment (never sent to the server or logged). Prepend the base URL the UI is served from (the Sparrow server with SPARROW_SERVE_UI=true, or your separately hosted UI) and hand it to the end consumer." type: string reused: description: True when external_id matched a still-valid token, which is returned instead of a new one. type: boolean secret: description: "The credential. Shown only in this response (and again for the same external_id); send it as X-API-Key or Authorization: Bearer." type: string token: $ref: "#/components/schemas/TokenOut" description: The created (or reused) token. required: - token - secret - reused type: object CrlOut: additionalProperties: false properties: entries: description: Revoked certificates it lists. format: int64 type: integer error: description: Why the last download or check failed. In soft mode such a CRL gives no answer and connections are allowed. type: string fetched_at: description: When this instance last downloaded it successfully. format: date-time type: string issuer: description: Common name of the CA that signs this CRL. type: string next_update: description: When the CA promises a newer CRL. Past it, the CRL is downloaded again before use. format: date-time type: string url: description: CRL distribution point, from an intermediate in a receiver's chain. type: string required: - url - issuer - entries type: object CursorPaginationOutput: additionalProperties: false properties: has_more: description: True when older items follow this page. type: boolean limit: description: Page size applied (the requested limit, default 50). format: int32 type: integer next_cursor: description: Pass as cursor to get the next, older page. Absent on the last page. type: string required: - limit - has_more type: object DeliveryItem: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/DeliveryItem.json format: uri readOnly: true type: string attempt_count: description: Number of delivery attempts made so far, including earlier rounds before a manual retry. format: int64 type: integer consumer: description: Consumer of the webhook. Set in delivery listings. type: string created_at: description: When the delivery was created by fan-out, RFC3339. format: date-time type: string delivery_id: description: Delivery id (UUID). type: string error_category: description: Outcome classification of the most recent attempt, used to decide retryability. server_error (5xx), timeout, connection_refused, network_error and rate_limited (429) are retried; client_error (other 4xx), dns_error, tls_error and unexpected_status (a 2xx/3xx not in expected_status_codes) are not. template_error means the subscription's transform failed to render; it is not retried automatically and does not count against the webhook's health. enum: - success - client_error - server_error - timeout - dns_error - tls_error - connection_refused - network_error - rate_limited - unexpected_status - template_error - unknown type: string error_message: description: Human-readable failure reason from the most recent attempt. type: string event_id: description: Pushed event occurrence this delivery originated from. type: string event_name: description: Event type of that occurrence, e.g. order.created. Set in delivery listings. type: string last_attempted_at: description: Timestamp of the most recent attempt, RFC3339. format: date-time type: string max_attempts: description: "Attempts allowed per round before the delivery is marked failed: the webhook's max_retries + 1 when the delivery was created. A manual retry starts a new round." format: int64 type: integer next_retry_at: description: Timestamp of the next scheduled retry, RFC3339, if one is pending. format: date-time type: string response_body: description: "Endpoint response body from the most recent attempt: up to 1 KiB, or up to SPARROW_MAX_CAPTURED_RESPONSE_BYTES when the webhook has capture_response_body." type: string response_code: description: HTTP status code returned by the endpoint on the most recent attempt, if any. format: int64 type: integer status: description: "Current delivery status. pending: queued for its first attempt (or re-queued by a retry). sending: an attempt is in flight. retrying: an attempt failed and another is scheduled (next_retry_at). success: an attempt got an expected status code. failed: attempts exhausted, or a non-retryable error such as template_error. expired: the event's ttl_seconds passed before the attempt. paused: held because the webhook or subscription was paused; not attempted until retried." enum: - pending - sending - success - failed - retrying - expired - paused type: string template_error: description: "The payload transform error, when the subscription's template failed to render. Set for both on_transform_error modes: with fail the delivery was not sent; with fallback the default envelope was sent instead." type: string webhook_description: description: Description of that webhook. Set in delivery listings. type: string webhook_id: description: Webhook this delivery was sent to. type: string webhook_url: description: URL of that webhook. Set in delivery listings. type: string required: - delivery_id - webhook_id - event_id - status - attempt_count - max_attempts - created_at type: object DraftTemplateBody: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/DraftTemplateBody.json format: uri readOnly: true type: string current_template: description: Optional existing template to refine instead of drafting from scratch. maxLength: 20000 type: string docs_url: description: Optional http(s) page documenting the receiver's payload. Sparrow fetches it from a public address only (loopback, private and cloud-metadata addresses are always refused, even when SPARROW_ALLOW_PRIVATE_NETWORKS lets deliveries reach them) and gives the model an excerpt of its text. format: uri maxLength: 2048 type: string event_name: description: Registered event type whose schema and sample payload ground the draft, and against which it is verified. type: string instructions: description: Plain-language description of the body the receiver should get, e.g. "a short Slack message with the order id and total, flagged when refunded". maxLength: 4000 minLength: 1 type: string recipe: description: Name of a shipped recipe (see GET /v1/recipes) whose destination format the draft should follow, e.g. "slack". Omit for a custom receiver. type: string sample_payload: additionalProperties: {} description: Optional payload to ground and verify the draft against instead of the event type's registered sample. Does not change the event type. type: object target_example: description: "Optional: what the receiver expects, either an example body (the draft maps event fields onto that exact structure) or a plain-language description of its shape." maxLength: 20000 type: string required: - event_name - instructions type: object DraftTemplateOutputBody: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/DraftTemplateOutputBody.json format: uri readOnly: true type: string attempts: description: Draft/render rounds it took; more than 1 means an earlier draft failed to render and was repaired. format: int64 type: integer model: description: Model id that produced the draft. type: string notes: description: Short explanation from the model of the fields used and assumptions made. type: string rendered: description: The template rendered against the sample payload used (provided or registered), proving it renders. type: string sample_source: description: "Which sample payload grounded and verified the draft: registered or provided." enum: - registered - provided type: string template: description: The drafted Go template, ready to save as transform_template. type: string required: - template - rendered - sample_source - attempts - model type: object DraftTemplatePromptOutputBody: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/DraftTemplatePromptOutputBody.json format: uri readOnly: true type: string prompt: description: "A self-contained prompt to paste into any chat assistant: the template data model, rules, helper catalog, the event's schema and sample payload, any recipe, receiver example, docs excerpt, current template, and the instructions. Asks for the template in a code block." type: string sample_source: description: "Which sample payload the prompt carries: registered (the event type's sample_payload) or provided (sample_payload from the request)." enum: - registered - provided type: string required: - prompt - sample_source type: object ErrorDetail: additionalProperties: false properties: location: description: Where the error occurred, e.g. 'body.items[3].tags' or 'path.thing-id' type: string message: description: Error message text type: string value: description: The value at the given location type: object ErrorModel: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/ErrorModel.json format: uri readOnly: true type: string detail: description: A human-readable explanation specific to this occurrence of the problem. examples: - Property foo is required but is missing. type: string errors: description: Optional list of individual error details items: $ref: "#/components/schemas/ErrorDetail" type: array instance: description: A URI reference that identifies the specific occurrence of the problem. examples: - https://example.com/error-log/abc123 format: uri type: string status: description: HTTP status code examples: - 400 format: int64 type: integer title: description: A short, human-readable summary of the problem type. This value should not change between occurrences of the error. examples: - Bad Request type: string type: default: about:blank description: A URI reference to human-readable documentation for the error. examples: - https://example.com/errors/example format: uri type: string type: object EventOccurrenceItem: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/EventOccurrenceItem.json format: uri readOnly: true type: string consumer: description: Tenant consumer this occurrence was pushed into. type: string created_at: description: When this occurrence was pushed, RFC3339. format: date-time type: string event: description: Event type name. type: string event_id: description: Event occurrence id (UUID). type: string event_version: description: Event type version this occurrence was accepted under. format: int64 type: integer failed_deliveries: description: "Failed delivery attempts for this occurrence. Counts attempts, not deliveries: a delivery that failed twice and then succeeded adds 2 here and 1 to successful_deliveries." format: int32 type: integer labels: additionalProperties: type: string description: Key/value labels used to match against subscription label filters. type: object metadata: additionalProperties: type: string description: Arbitrary key/value metadata stored with the occurrence. type: object payload: additionalProperties: {} description: The pushed payload. type: object pending_deliveries: description: "Deliveries not finished yet: pending, sending or retrying." format: int32 type: integer scheduled_at: description: When this occurrence fans out to subscriptions, RFC3339. Absent for immediate pushes. Deliveries exist only from this time on. format: date-time type: string schema_valid: description: Whether the payload validated against the event type's JSON Schema at push time. type: boolean successful_deliveries: description: Successful delivery attempts for this occurrence. format: int32 type: integer webhook_count: description: Number of distinct webhooks that got a delivery for this occurrence. format: int32 type: integer required: - event_id - consumer - event - payload - schema_valid - event_version - webhook_count - successful_deliveries - failed_deliveries - pending_deliveries - created_at type: object EventTypeBody: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/EventTypeBody.json format: uri readOnly: true type: string active: description: Whether events of this type can be pushed; a push to an inactive type fails with 409. Defaults to true on register; unchanged on update when omitted. type: boolean description: description: Human-readable summary of what this event represents. On update, an empty string leaves it unchanged. type: string event_schema: additionalProperties: {} description: "Optional JSON Schema for the payload. Validation is soft: non-conforming payloads are still accepted, with warnings returned from the push call. On update, sending a different schema creates a new version." type: object metadata: additionalProperties: type: string description: Arbitrary key/value metadata for your own tooling. On update, replaces the whole map. type: object name: description: Unique event type name, e.g. order.created. Required on register, ignored on update (the path names the type). Lowercase letters, digits, '_' and '-' in dot-separated parts (e.g. stripe.payment_intent.succeeded), at most 255 characters; the sparrow. prefix is reserved. Immutable after creation, and event types cannot be deleted. type: string type: object EventTypeBundle: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/EventTypeBundle.json format: uri readOnly: true type: string apiVersion: description: Bundle schema version. enum: - sparrow/v1 type: string items: description: Event type definitions, sorted by name. items: $ref: "#/components/schemas/BundleItem" type: array kind: description: Bundle kind. enum: - EventTypeList type: string stamp: $ref: "#/components/schemas/BundleStampBody" description: Which Sparrow produced this bundle. A compatibility hint, not a signature. required: - apiVersion - kind - stamp - items type: object EventTypeChange: additionalProperties: false properties: action: description: "created: new event type at version 1. new_version: the schema changed, so a new version was created and the previous one kept. updated: the current version changed in place (description, metadata, active, or a first schema added to a schema-less version). unchanged: nothing was written." enum: - created - new_version - updated - unchanged type: string active_change: description: Set when the write flips the active flag. enum: - deactivates - reactivates type: string affected_subscriptions: description: "For new_version: subscriptions that receive this event type, by name or catch-all." format: int64 type: integer changes: description: "Fields that changed: schema, schema_defined (a first schema was added in place), description, metadata, active." items: type: string type: array compatibility: $ref: "#/components/schemas/SchemaCompatibilityItem" description: "For new_version: whether the schema change could break a subscription's payload transformation." previous_version: description: Version before the write; absent when the event type was created. format: int64 type: integer version: description: Version after the write. format: int64 type: integer required: - action - version type: object EventTypeItem: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/EventTypeItem.json format: uri readOnly: true type: string active: description: Whether events of this type can currently be pushed. type: boolean created_at: description: Creation timestamp, RFC3339. format: date-time type: string description: description: Human-readable summary of what this event represents. type: string event_schema: additionalProperties: {} description: JSON Schema payloads are softly validated against, if one is registered. type: object metadata: additionalProperties: type: string description: Arbitrary key/value metadata. type: object name: description: Event type name, e.g. order.created. type: string sample_payload: additionalProperties: {} description: Example payload derived from the schema, used to preview subscription transform templates. type: object updated_at: description: Last-modified timestamp, RFC3339. format: date-time type: string version: description: Current schema version. Starts at 1 and increases only when the schema changes; every version is kept (see listEventTypeVersions). format: int64 type: integer required: - name - active - version - created_at - updated_at type: object EventTypeVersionItem: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/EventTypeVersionItem.json format: uri readOnly: true type: string created_at: description: When this version was created, RFC3339. format: date-time type: string description: description: Description as it was when this version was created. type: string event_schema: additionalProperties: {} description: JSON Schema of this version. Absent for a schema-less version. type: object name: description: Event type name. type: string sample_payload: additionalProperties: {} description: Example payload generated from this version's schema. type: object schema_defined_at: description: When a first schema was added to this version in place, RFC3339. Events pushed before this time were accepted while the version had no schema. format: date-time type: string version: description: Version number. format: int64 type: integer required: - name - version - created_at type: object ExportEventTypesInputBody: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/ExportEventTypesInputBody.json format: uri readOnly: true type: string all: description: Export every event type. type: boolean names: description: Export these event types. Each must exist. items: type: string type: array prefix: description: Export every event type whose name starts with this prefix. type: string type: object HealthRules: additionalProperties: false properties: degraded_min_attempts: description: Attempts in the window needed before a low success rate counts as degraded. format: int64 type: integer degraded_success_rate: description: A success rate below this (0.0 to 1.0), over at least degraded_min_attempts attempts, is degraded. format: double type: number healthy_min_attempts: description: Attempts at or above degraded_success_rate needed to call a webhook healthy; with fewer it stays unknown. format: int64 type: integer unhealthy_consecutive_failures: description: Failed attempts in a row that make a webhook unhealthy whatever its success rate. format: int64 type: integer unhealthy_min_attempts: description: Attempts in the window needed before a low success rate counts as unhealthy. format: int64 type: integer unhealthy_success_rate: description: A success rate below this (0.0 to 1.0), over at least unhealthy_min_attempts attempts, is unhealthy. format: double type: number window_hours: description: Lookback window, in hours, of the attempt counts and success rate. format: int64 type: integer required: - window_hours - unhealthy_consecutive_failures - unhealthy_success_rate - unhealthy_min_attempts - degraded_success_rate - degraded_min_attempts - healthy_min_attempts type: object HealthSummaryOutputBody: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/HealthSummaryOutputBody.json format: uri readOnly: true type: string degraded_count: description: Webhooks currently labelled degraded. format: int64 type: integer healthy_count: description: Webhooks currently labelled healthy. format: int64 type: integer rules: $ref: "#/components/schemas/HealthRules" description: Thresholds the health statuses are derived from. unhealthy_count: description: Webhooks currently labelled unhealthy. format: int64 type: integer unknown_count: description: "Webhooks labelled unknown: too few recent attempts to judge." format: int64 type: integer required: - healthy_count - degraded_count - unhealthy_count - unknown_count - rules type: object ImportEventTypesInputBody: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/ImportEventTypesInputBody.json format: uri readOnly: true type: string acknowledge: description: Stamp warnings you accept. Each unacknowledged warning blocks the write. items: enum: - version_differs - format_unsupported - items_changed type: string type: array allow_breaking: description: Apply breaking schema changes to event types that subscriptions receive. type: boolean apiVersion: description: Bundle schema version, if the body is an exported bundle. enum: - sparrow/v1 type: string dry_run: description: Compute the full result without writing anything. type: boolean items: description: Definitions to import. Each replaces the named event type; types not listed are never touched. items: $ref: "#/components/schemas/BundleItem" maxItems: 500 minItems: 1 type: array kind: description: Bundle kind, if the body is an exported bundle. enum: - EventTypeList type: string stamp: $ref: "#/components/schemas/BundleStampBody" description: The bundle's stamp. Absent for a hand-written bundle, which imports with a notice. subscription_policy: description: "keep_active (default): subscriptions keep running; a template that no longer fits fails its deliveries visibly with template_error. pause: in the same transaction, pause each subscription whose template fails the pre-check, with the import as the reason." enum: - keep_active - pause type: string required: - items type: object ImportEventTypesOutputBody: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/ImportEventTypesOutputBody.json format: uri readOnly: true type: string applied: description: True when the import was written. False for a dry run or when blocked_by is non-empty. type: boolean blocked_by: description: "Why nothing was written: unacknowledged stamp warnings, and breaking when a breaking change needs allow_breaking." items: type: string type: array dry_run: description: Whether this was a dry run. type: boolean imported_at: description: When the import was written, RFC3339. Use it to find deliveries that failed after the change. format: date-time type: string items: description: One result per bundle item, sorted by name. items: $ref: "#/components/schemas/ImportItemResult" type: array stamp: $ref: "#/components/schemas/StampCheckItem" description: The bundle's stamp compared with this server. required: - applied - dry_run - stamp - items type: object ImportItemResult: additionalProperties: false properties: action: description: What the import does to this event type. See updateEventType's change.action. enum: - created - new_version - updated - unchanged type: string active_change: description: Set when the import flips the active flag. enum: - deactivates - reactivates type: string blocked: description: The change is breaking for subscriptions and allow_breaking was not set. type: boolean changes: description: "Fields that change: schema, schema_defined, description, metadata, active." items: type: string type: array compatibility: $ref: "#/components/schemas/SchemaCompatibilityItem" description: "For new_version: whether the change could break a subscription's payload transformation." name: description: Event type name. type: string previous_version: description: Version before the import. format: int64 type: integer subscriptions: $ref: "#/components/schemas/TemplateCheckItem" description: "For new_version: every affected subscription's transform rendered strictly against the new version." version: description: Version after the import (or that it would have, for a dry run). format: int64 type: integer required: - name - action - version type: object InviteOut: additionalProperties: false properties: cancelled_at: description: "When the invite was cancelled, RFC3339. Null: not cancelled." format: date-time type: - string - "null" consumer: description: "Consumer the resulting token is limited to. Null: tenant-wide." type: - string - "null" created_at: description: When the invite was created, RFC3339. format: date-time type: string created_by: description: Name of the credential that created the invite. type: string expires_at: description: When the invite stops working if unused, RFC3339. format: date-time type: string id: description: Invite id (not the secret). type: string name: description: Name given to the token the invite creates. type: string redeemed_at: description: "When the invite was redeemed, RFC3339. Null: not redeemed." format: date-time type: - string - "null" status: description: "pending: can still be redeemed. redeemed: used once, see token_id. cancelled: cancelled by DELETE /v1/invites/{invite_id}. expired: past expires_at unused." enum: - pending - redeemed - cancelled - expired type: string token_id: description: Token created when the invite was redeemed. type: - string - "null" token_ttl_seconds: description: "Lifetime of the token it creates, in seconds. Null: the token never expires." format: int64 type: - integer - "null" required: - id - name - consumer - status - token_ttl_seconds - created_by - created_at - expires_at - redeemed_at - cancelled_at - token_id type: object ListAlertConfigsOutputBody: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/ListAlertConfigsOutputBody.json format: uri readOnly: true type: string items: description: Alert configs, oldest first. Not paginated. items: $ref: "#/components/schemas/AlertConfigItem" type: array required: - items type: object ListConsumersOutputBody: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/ListConsumersOutputBody.json format: uri readOnly: true type: string items: description: Matching consumer names, sorted by name. items: $ref: "#/components/schemas/ConsumerItem" type: array required: - items type: object ListCrlsOutputBody: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/ListCrlsOutputBody.json format: uri readOnly: true type: string crls: description: CRLs this instance has cached, by URL. Each instance keeps its own cache. items: $ref: "#/components/schemas/CrlOut" type: array mode: description: "SPARROW_TLS_REVOCATION: soft allows a connection whose CRL is unavailable, hard refuses it, off skips the check." enum: - soft - hard - "off" type: string required: - mode - crls type: object ListDeliveriesOutputBody: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/ListDeliveriesOutputBody.json format: uri readOnly: true type: string items: description: Deliveries, newest first. Includes deliveries of deleted webhooks. items: $ref: "#/components/schemas/DeliveryItem" type: array pagination: $ref: "#/components/schemas/CursorPaginationOutput" description: Cursor pagination; there is no total count. retry_id: description: Snapshot (batch job) id, present when prepare_retry was set and something matched. Send it as the retry_id field of POST .../deliveries:retryBatch. Expires after 15 minutes if not started. type: string retry_total: description: Number of deliveries in that snapshot (all matches, not just this page). format: int64 type: integer required: - items - pagination type: object ListEventOccurrencesOutputBody: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/ListEventOccurrencesOutputBody.json format: uri readOnly: true type: string items: description: Occurrences, newest first. items: $ref: "#/components/schemas/EventOccurrenceItem" type: array pagination: $ref: "#/components/schemas/CursorPaginationOutput" description: Cursor pagination; there is no total count. repush_id: description: Snapshot (batch job) id for POST .../events:rePush, present when prepare_repush was set and something matched. Expires after 15 minutes if not started. type: string repush_total: description: Number of occurrences in that snapshot (all matches, not just this page). format: int64 type: integer required: - items - pagination type: object ListEventTypeVersionsOutputBody: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/ListEventTypeVersionsOutputBody.json format: uri readOnly: true type: string items: description: Every version of the event type, newest first. items: $ref: "#/components/schemas/EventTypeVersionItem" type: array required: - items type: object ListEventTypesOutputBody: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/ListEventTypesOutputBody.json format: uri readOnly: true type: string items: description: Event types, sorted by name. items: $ref: "#/components/schemas/EventTypeItem" type: array pagination: $ref: "#/components/schemas/PaginationOutput" description: Offset pagination with a total count. required: - items - pagination type: object ListInvitesOutputBody: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/ListInvitesOutputBody.json format: uri readOnly: true type: string items: description: Invites, newest first. Not paginated. items: $ref: "#/components/schemas/InviteOut" type: array required: - items type: object ListRecipesOutputBody: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/ListRecipesOutputBody.json format: uri readOnly: true type: string items: description: Every shipped recipe. items: $ref: "#/components/schemas/Recipe" type: array required: - items type: object ListSubscriptionsOutputBody: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/ListSubscriptionsOutputBody.json format: uri readOnly: true type: string items: description: Subscriptions, newest first. items: $ref: "#/components/schemas/SubscriptionItem" type: array pagination: $ref: "#/components/schemas/PaginationOutput" description: Offset pagination with a total count. required: - items - pagination type: object ListTemplateVersionsOutputBody: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/ListTemplateVersionsOutputBody.json format: uri readOnly: true type: string items: description: Saved versions, newest first. items: $ref: "#/components/schemas/TemplateVersionItem" type: array required: - items type: object ListTokensOutputBody: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/ListTokensOutputBody.json format: uri readOnly: true type: string items: description: Tokens, newest first. Not paginated. items: $ref: "#/components/schemas/TokenOut" type: array required: - items type: object ListWebhooksOutputBody: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/ListWebhooksOutputBody.json format: uri readOnly: true type: string items: description: Webhooks, newest first. items: $ref: "#/components/schemas/WebhookOut" type: array pagination: $ref: "#/components/schemas/PaginationOutput" description: Offset pagination with a total count. required: - items - pagination type: object PaginationOutput: additionalProperties: false properties: has_more: description: True when offset + limit < total_count. type: boolean limit: description: Page size applied (the requested limit, default 50). format: int32 type: integer offset: description: Number of items skipped. format: int32 type: integer total_count: description: Total items matching the filters. format: int32 type: integer required: - limit - offset - total_count - has_more type: object Param: additionalProperties: false properties: activation_required: description: A missing value blocks enabling an auto-provisioned recipe. type: boolean default: description: Value used when none is supplied. type: string docs_url: description: Destination documentation for this value. type: string enum: description: The only accepted values, when set. items: type: string type: array example: description: A sample value. type: string help: description: What the value is and where to find it. type: string must_override_default: description: "The default is a placeholder: it must be replaced with a real value before the recipe is applied." type: boolean name: description: Param name, as referenced by {{param "name"}}. type: string prompt: description: Short label shown for the param. type: string required: description: Whether a value must be supplied. type: boolean secret: description: Whether the value is sensitive (a token or password); clients should mask the input. type: boolean required: - name - prompt - required type: object PatchSubscriptionBody: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/PatchSubscriptionBody.json format: uri readOnly: true type: string headers: additionalProperties: type: string description: Replace all extra HTTP headers. type: object label_filters: additionalProperties: type: string description: Replace all label filters; an empty object matches every event. type: object method: description: Replace the HTTP method. type: string on_transform_error: description: Replace what happens when the template fails to render. enum: - fail - fallback type: string template_missing_key: description: Replace how the template reads a missing key. enum: - error - zero type: string template_notes: description: Optional note stored with the template version, e.g. the AI drafter's summary. maxLength: 2000 type: string template_source: description: How a changed transform_template was produced, recorded in the subscription's template history. Defaults to manual. enum: - manual - ai_draft type: string timeout: description: Replace the request timeout override, in seconds (0 to 300; 0 uses the webhook's). format: int64 type: integer transform_enabled: description: Enable or disable payload transformation. type: boolean transform_template: description: Replace the transform template. A template that does not parse is rejected with 400. A change is recorded as a new version in the template history (the last 20 are kept). type: string type: object PatchWebhookBody: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/PatchWebhookBody.json format: uri readOnly: true type: string active: description: false pauses the webhook (like :pause), true resumes it and clears any auto-disable marker (like :resume, but without returning the held count). type: boolean description: description: Replace the human-readable description. An empty string leaves it unchanged. type: string events: description: Replace the full set of subscribed event type names. Every existing subscription of the webhook is deleted, with its transform and label filters, and one plain subscription is created per name. An empty list leaves subscriptions unchanged. Rejected with 409 while requires_transform is on; to keep transforms, add or delete subscriptions individually. items: type: string type: array headers: additionalProperties: type: string description: Replace all static headers sent with every delivery. Same rules as on register. type: object http_config: $ref: "#/components/schemas/WebhookHTTPConfig" description: Update HTTP delivery settings. Only the fields you send change; the rest keep their current values. The merged result must satisfy the same limits as on create, or the request is rejected with 400. requires_transform: description: "Require (or stop requiring) a payload transform on every subscription. Turning it on fails with 409 while any subscription has no enabled transform_template. While on, events cannot be replaced in bulk (409): add or delete subscriptions individually." type: boolean secret_headers: additionalProperties: type: string description: Merge-patch encrypted secret headers by name. Send a new value to replace one header, omit a key to leave it untouched, or send an empty string to remove it. Names match in any case. An empty object removes them all. Changing url to another origin (scheme, host or port) is refused unless this sets every existing secret header again (a new value, or an empty string to remove it), so moving a webhook never carries its secrets to a new server. type: object signature_type: description: "Change the signing scheme: hmac or ed25519. Switching to ed25519 generates a signing keypair; HMAC signing continues either way." type: string timeout: deprecated: true description: "Deprecated: use http_config.request_timeout_seconds. Shorthand for it, in seconds (1 to 300), applied only when http_config.request_timeout_seconds is not sent (http_config wins when both are sent)." format: int64 type: integer url: description: Replace the delivery endpoint URL. Same rules as on register. type: string type: object PauseSubscriptionInputBody: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/PauseSubscriptionInputBody.json format: uri readOnly: true type: string reason: description: "Why the subscription is paused, shown next to it, for example: receiver maintenance until Friday." maxLength: 500 type: string type: object PortalStruct: additionalProperties: false properties: retry: description: True when portal tokens may retry their deliveries (SPARROW_PORTAL_RETRY, default true). When false, the retry calls under /portal/api/ answer 403; admins can still retry. type: boolean required: - retry type: object PushEventBody: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/PushEventBody.json format: uri readOnly: true type: string idempotency_key: description: "Client-supplied deduplication key, unique per consumer. A push whose key matches an earlier event in the same consumer still returns 201, with duplicate: true and the original event_id, and creates no event or deliveries. The key is kept as long as the event (see SPARROW_EVENT_RETENTION_DAYS)." type: string labels: additionalProperties: type: string description: Key/value labels a subscription's label_filters match against to decide which subscriptions receive this occurrence. At most 20 pairs; keys up to 64 characters of letters, digits, '.', '_' and '-'; values up to 256 characters. type: object metadata: additionalProperties: type: string description: Arbitrary key/value metadata stored with the occurrence. type: object payload: additionalProperties: {} description: Event payload, a JSON object. Validated softly against the event type's JSON Schema if one is registered. The whole request body is limited to 1 MiB (413 above that). type: object scheduled_at: description: RFC 3339 time to deliver the event at. Omitted or in the past means immediate. The event is stored right away but fans out to subscriptions only at this time; ttl_seconds counts from then. At most one year ahead (400 beyond that). Replays of a scheduled occurrence are immediate. format: date-time type: string ttl_seconds: description: Time-to-live in seconds, counted from fan-out. A delivery attempt that would start after it expires is not made and the delivery fails as expired. 0 or omitted means no expiry. Manual retries ignore it. format: int64 type: integer required: - payload type: object PushEventOutputBody: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/PushEventOutputBody.json format: uri readOnly: true type: string duplicate: description: True when idempotency_key matched an existing event occurrence in this consumer; no new event or deliveries were created and the status is still 201. type: boolean event_id: description: Id (UUID) of the created event occurrence. When duplicate is true, this is the id of the PREVIOUSLY created event, not a new one. type: string schema_valid: description: Whether the payload validated against the event type's JSON Schema (true when it has none). False does not mean the push was rejected; see warnings. For a duplicate, the original event's result. type: boolean warnings: description: Non-fatal schema validation warnings. The event is stored and delivered regardless. Absent for a duplicate. items: type: string type: array required: - event_id - duplicate - schema_valid type: object Recipe: additionalProperties: false properties: consumer: $ref: "#/components/schemas/ConsumerHint" description: The consumer the recipe is meant to be registered under, when it matters. description: description: One-line summary of the destination the recipe targets. type: string guidance: description: What an ideal message for this destination looks like. Advice for template authors and AI drafting, not a rule. type: string name: description: Recipe name, e.g. slack. Unique among shipped recipes. type: string params: description: Values the user supplies when applying the recipe. Each {{param "name"}} token in webhook.url, webhook.headers, webhook.secret_headers and subscription.transform_template is replaced with the value by plain string substitution before registration. items: $ref: "#/components/schemas/Param" type: array subscription: $ref: "#/components/schemas/Subscription" description: The transform given to the subscriptions created for the webhook. version: description: Recipe schema version. Always 1. format: int64 type: integer webhook: $ref: "#/components/schemas/Webhook" description: The webhook to register. required: - version - name - description - webhook - subscription type: object RegisterWebhookBody: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/RegisterWebhookBody.json format: uri readOnly: true type: string active: description: Whether the webhook is sent deliveries. Defaults to true. An inactive (paused) webhook still gets deliveries recorded for new events, held with status paused and sent only when retried. type: boolean description: description: Free-text note for humans, e.g. which system or team owns this endpoint. type: string events: description: Event type names this webhook should receive; auto-creates one subscription per entry. Use "*" as the sole entry to subscribe to every event in the consumer. Omit or leave empty to register the webhook with no subscriptions, then attach them individually via POST .../subscriptions (e.g. to set a per-subscription transform_template). items: type: string type: array headers: additionalProperties: {} description: "Static HTTP headers sent with every delivery to this webhook. Only string values are sent. Names must be valid HTTP tokens, values at most 8 KiB with no CR/LF; framing headers (Host, Content-Length, Transfer-Encoding, Connection, Keep-Alive, Proxy-Connection, TE, Trailer, Upgrade) are rejected. Stored and returned in plaintext: put credentials in secret_headers." type: object http_config: $ref: "#/components/schemas/WebhookHTTPConfig" description: Per-webhook HTTP delivery tuning (retries, timeouts, rate limit). Falls back to server defaults for any field left unset. on_transform_error: description: What happens when transform_template fails to render for one of the created subscriptions. fail (default) or fallback; see the subscription's on_transform_error. enum: - fail - fallback type: string rate_limit_rps: deprecated: true description: "Deprecated: use http_config.rate_limit_rps. Shorthand for it, applied only when http_config.rate_limit_rps is not set (http_config wins when both are sent). Requests per second, must be positive." format: double type: number requires_transform: description: "Set when the receiver only accepts a transformed payload (Slack, SendGrid, ...). Every subscription must then have transform_enabled and a transform_template: transform_template is required here when events is non-empty, and later subscription writes without one are rejected. Recipes set this. Defaults to false." type: boolean secret_headers: additionalProperties: type: string description: HTTP headers sent with every delivery whose values are envelope-encrypted at rest and masked in every API response (e.g. an upstream auth token). Same name and value rules as headers. Names are case-insensitive and saved in canonical form (x-api-key becomes X-Api-Key); the same name twice in different case is refused. type: object signature_type: description: Signing scheme. Every delivery carries an HMAC-SHA256 signature (v1,) in the Standard Webhooks webhook-signature header; ed25519 also generates a signing keypair and adds an Ed25519 signature (v1a,) next to it. Defaults to hmac. enum: - hmac - ed25519 type: string template_missing_key: description: How transform_template reads a key the payload does not have, for the created subscriptions. error (default) or zero; see the subscription's template_missing_key. enum: - error - zero type: string template_notes: description: Optional note stored with that first template version, e.g. the AI drafter's summary. maxLength: 2000 type: string template_source: description: How transform_template was produced, recorded as the first version in each created subscription's template history. Defaults to manual. enum: - manual - ai_draft type: string transform_template: description: Transform template given to every subscription created from events, with transform_enabled set. A template that does not parse is rejected with 400. Omit to create them untransformed (not allowed with requires_transform). type: string url: description: "http or https endpoint deliveries are POSTed to. The host must resolve: localhost, *.internal, *.local, private, loopback and cloud metadata addresses are rejected (SSRF protection) unless allowed by SPARROW_ALLOW_PRIVATE_NETWORKS or SPARROW_ALLOWED_NETWORKS." format: uri type: string required: - url type: object RepushBatchInputBody: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/RepushBatchInputBody.json format: uri readOnly: true type: string repush_id: description: repush_id returned by an earlier GET .../events?prepare_repush=true call. type: string required: - repush_id type: object RepushEventOutputBody: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/RepushEventOutputBody.json format: uri readOnly: true type: string event_id: description: Id (UUID) of the new event occurrence created by the replay. The original occurrence is unchanged. type: string warnings: description: Non-fatal warnings from validating the stored payload against the event type's current schema. items: type: string type: array required: - event_id type: object ResumeSubscriptionOutputBody: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/ResumeSubscriptionOutputBody.json format: uri readOnly: true type: string consumer: description: Consumer this subscription belongs to. type: string created_at: description: Creation timestamp, RFC3339. format: date-time type: string event_name: description: Event type name this subscription matches, or "*" for catch-all. type: string headers: additionalProperties: type: string description: Extra HTTP headers sent with deliveries from this subscription. type: object label_filters: additionalProperties: type: string description: Key/value pairs that must all be present in an event's labels for this subscription to receive it. type: object method: description: HTTP method used for deliveries from this subscription; absent means POST. enum: - GET - POST - PUT - PATCH - DELETE type: string on_transform_error: description: "What happens when transform_template fails to render. fail (default): the delivery fails with error category template_error, is not retried automatically, and can be retried once the template is fixed; it does not affect the webhook's health. fallback: the default envelope payload is sent instead, and the error is still recorded on the delivery." enum: - fail - fallback type: string paused: description: Whether the subscription is paused. While paused, events still fan out to it, but each delivery is recorded with status paused and not attempted until retried. A pause never affects the webhook's health. type: boolean paused_at: description: When the current pause began, RFC3339. Absent when not paused. format: date-time type: string paused_deliveries: description: Deliveries of this subscription currently in status paused. They are not sent automatically; retry them to deliver. format: int64 type: integer paused_reason: description: Why the subscription was paused. type: string paused_since: description: When the pause that just ended began, RFC3339; absent when the subscription was not paused. List or retry what was held with status=paused&subscription_id=...&created_after=. format: date-time type: string subscription_id: description: Subscription id (UUID). type: string template_missing_key: description: "How transform_template reads a key the payload does not have. error (default): the render fails, so a field removed from the event schema cannot silently turn into \"\" in the body; read optional fields with index or dig (default only fills a key that is present but empty). zero: the key renders as \"\"." enum: - error - zero type: string timeout: description: Request timeout override for this subscription's deliveries, in seconds; absent means the webhook's request_timeout_seconds. format: int64 type: integer transform_enabled: description: Whether transform_template is rendered into the delivered payload. type: boolean transform_template: description: Go text/template rendered against the event to produce the delivered body when transform_enabled is true. type: string updated_at: description: Last-modified timestamp, RFC3339. format: date-time type: string webhook_id: description: Webhook this subscription delivers to. type: string required: - paused_deliveries - subscription_id - consumer - webhook_id - event_name - transform_enabled - on_transform_error - template_missing_key - paused - created_at - updated_at type: object ResumeWebhookOutputBody: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/ResumeWebhookOutputBody.json format: uri readOnly: true type: string paused_deliveries: description: Deliveries held as paused for this webhook. They are not sent automatically; retry them to deliver. format: int64 type: integer required: - paused_deliveries type: object RetryBatchInputBody: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/RetryBatchInputBody.json format: uri readOnly: true type: string retry_id: description: retry_id returned by an earlier GET .../deliveries?prepare_retry=true call. type: string required: - retry_id type: object RetryDeliveriesByWebhookInputBody: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/RetryDeliveriesByWebhookInputBody.json format: uri readOnly: true type: string force: description: false (default) retries deliveries in status failed, pending, retrying or paused. true retries every delivery of the webhook, including successful and expired ones, which resends them. type: boolean webhook_id: description: Webhook id (UUID) whose deliveries to retry. type: string required: - webhook_id type: object RetryOutputBody: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/RetryOutputBody.json format: uri readOnly: true type: string count: description: Number of deliveries queued for another round of attempts. format: int32 type: integer delivery_ids: description: Ids of the deliveries queued. Absent when nothing matched. items: type: string type: array required: - count type: object RotateWebhookSecretOutputBody: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/RotateWebhookSecretOutputBody.json format: uri readOnly: true type: string webhook_secret: description: The new HMAC signing secret, in plaintext. Shown only in this response. type: string required: - webhook_secret type: object SchemaCompatibilityItem: additionalProperties: false properties: reasons: description: Each breaking change with the path it affects. items: type: string type: array result: description: "breaking when the change could break a payload transformation: a required property removed or made optional, a type widened, the schema removed, or a change under oneOf/anyOf/allOf/$ref/patternProperties that cannot be checked." enum: - compatible - breaking type: string required: - result type: object SendTestEventInputBody: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/SendTestEventInputBody.json format: uri readOnly: true type: string event_name: description: "Event to send: the subscription's event, or any event for a catch-all (*) subscription." type: string payload: additionalProperties: {} description: Event payload to send instead of the event type's sample payload. It is rendered through the subscription's transform like a pushed event. type: object required: - event_name type: object SendTestEventOutputBody: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/SendTestEventOutputBody.json format: uri readOnly: true type: string error_class: description: "Delivery error category when the send did not get a 2xx: client_error, server_error, rate_limited, timeout, connection_refused, dns_error, tls_error, network_error, redirect, template_error (nothing was sent) or unknown." type: string latency_ms: description: Time to the response headers, in milliseconds. format: int64 type: integer status_code: description: The receiver's HTTP status; 0 when no response was received. format: int64 type: integer required: - status_code - latency_ms type: object StampCheckItem: additionalProperties: false properties: exported_by: description: Sparrow version that exported the file. type: string server: description: This server's Sparrow version. type: string status: description: "valid: stamp present and matching. unsigned: no stamp (hand-written file). warning: see warnings." enum: - valid - unsigned - warning type: string warnings: description: version_differs, format_unsupported, items_changed. Each must be acknowledged for the import to write. items: type: string type: array required: - status - server type: object Subscription: additionalProperties: false properties: transform_template: description: Go template rendered per delivery to build the destination-native body; may contain {{param}} tokens. type: string required: - transform_template type: object SubscriptionItem: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/SubscriptionItem.json format: uri readOnly: true type: string consumer: description: Consumer this subscription belongs to. type: string created_at: description: Creation timestamp, RFC3339. format: date-time type: string event_name: description: Event type name this subscription matches, or "*" for catch-all. type: string headers: additionalProperties: type: string description: Extra HTTP headers sent with deliveries from this subscription. type: object label_filters: additionalProperties: type: string description: Key/value pairs that must all be present in an event's labels for this subscription to receive it. type: object method: description: HTTP method used for deliveries from this subscription; absent means POST. enum: - GET - POST - PUT - PATCH - DELETE type: string on_transform_error: description: "What happens when transform_template fails to render. fail (default): the delivery fails with error category template_error, is not retried automatically, and can be retried once the template is fixed; it does not affect the webhook's health. fallback: the default envelope payload is sent instead, and the error is still recorded on the delivery." enum: - fail - fallback type: string paused: description: Whether the subscription is paused. While paused, events still fan out to it, but each delivery is recorded with status paused and not attempted until retried. A pause never affects the webhook's health. type: boolean paused_at: description: When the current pause began, RFC3339. Absent when not paused. format: date-time type: string paused_reason: description: Why the subscription was paused. type: string subscription_id: description: Subscription id (UUID). type: string template_missing_key: description: "How transform_template reads a key the payload does not have. error (default): the render fails, so a field removed from the event schema cannot silently turn into \"\" in the body; read optional fields with index or dig (default only fills a key that is present but empty). zero: the key renders as \"\"." enum: - error - zero type: string timeout: description: Request timeout override for this subscription's deliveries, in seconds; absent means the webhook's request_timeout_seconds. format: int64 type: integer transform_enabled: description: Whether transform_template is rendered into the delivered payload. type: boolean transform_template: description: Go text/template rendered against the event to produce the delivered body when transform_enabled is true. type: string updated_at: description: Last-modified timestamp, RFC3339. format: date-time type: string webhook_id: description: Webhook this subscription delivers to. type: string required: - subscription_id - consumer - webhook_id - event_name - transform_enabled - on_transform_error - template_missing_key - paused - created_at - updated_at type: object TemplateCheckItem: additionalProperties: false properties: catch_all: description: How many of the affected subscriptions are catch-all ("*"). format: int64 type: integer failures: description: Subscriptions whose transform no longer renders against the new version. items: $ref: "#/components/schemas/TemplateFailureItem" type: array passed: description: Subscriptions whose transform renders. format: int64 type: integer paused: description: Subscriptions this import paused (subscription_policy=pause). items: type: string type: array without_transform: description: Subscriptions with no transform. Sparrow passes the payload through and cannot tell whether the receiver copes. format: int64 type: integer required: - passed - without_transform - catch_all type: object TemplateFailureItem: additionalProperties: false properties: consumer: description: Consumer the subscription belongs to. type: string error: description: The render error. type: string payload: description: "Which generated payload failed: every field, or only required fields (a template reading an optional field without guarding for it)." enum: - sample - required_only type: string subscription_id: description: Subscription whose template failed. type: string webhook_id: description: Webhook the subscription delivers to. type: string required: - subscription_id - consumer - webhook_id - payload - error type: object TemplateFunctionItem: additionalProperties: false properties: description: description: Markdown documentation for the function, including usage and an example. type: string name: description: Function name, as used in a transform_template. type: string required: - name - description type: object TemplateFunctionsOutputBody: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/TemplateFunctionsOutputBody.json format: uri readOnly: true type: string items: description: Every helper function, in the engine's own order. items: $ref: "#/components/schemas/TemplateFunctionItem" type: array required: - items type: object TemplateVersionItem: additionalProperties: false properties: created_at: description: When it was saved, RFC3339. format: date-time type: string current: description: True for the newest version, which is the subscription's current template. type: boolean notes: description: Note stored with the version. type: string saved_by: description: Name of the master key or access token that saved it; absent when auth is off. type: string source: description: "How the template was produced: manual or ai_draft." enum: - manual - ai_draft type: string template: description: The template text as saved. type: string version_id: description: Version id (UUID). type: string required: - version_id - template - source - created_at - current type: object TestTemplateBody: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/TestTemplateBody.json format: uri readOnly: true type: string event_name: description: Registered event type. Its name is the envelope's event_name, and its sample_payload (generated from its schema; empty without one) is rendered against when payload is omitted. type: string payload: additionalProperties: {} description: Payload to render against instead of the event type's generated sample, e.g. a real event's payload from listEventOccurrences. Used as given, even when empty; the event type needs no schema. type: object template: description: Go template to render, in the same syntax used by transform_template. type: string template_missing_key: description: Render as a subscription with this template_missing_key would. Defaults to error, the subscription default. enum: - error - zero type: string required: - event_name - template type: object TestTemplateOutputBody: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/TestTemplateOutputBody.json format: uri readOnly: true type: string rendered: description: The template output, rendered against payload (or the event type's sample payload) with a placeholder event id and the current time. type: string required: - rendered type: object TokenOut: additionalProperties: false properties: consumer: description: "Consumer the token is limited to (portal API only). Null: tenant-wide, same power as SPARROW_API_KEY." type: - string - "null" created_at: description: When the token was created, RFC3339. format: date-time type: string created_by: description: Name of the credential that created it (the master key, or another token). type: string expires_at: description: "When the token stops working, RFC3339. Null: never expires." format: date-time type: - string - "null" id: description: Token id (not the secret). type: string last_used_at: description: "When the token was last used, RFC3339; approximate (updated at most once a minute). Null: never used." format: date-time type: - string - "null" name: description: Who or what the token is for. type: string revoked_at: description: "When the token was revoked, RFC3339. Null: not revoked." format: date-time type: - string - "null" status: description: "active: usable. revoked: revoked by DELETE /v1/tokens/{token_id}. expired: past expires_at." enum: - active - revoked - expired type: string required: - id - name - consumer - status - created_by - created_at - expires_at - revoked_at - last_used_at type: object UpdateEventTypeOutputBody: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/UpdateEventTypeOutputBody.json format: uri readOnly: true type: string active: description: Whether events of this type can currently be pushed. type: boolean change: $ref: "#/components/schemas/EventTypeChange" description: What this update did. created_at: description: Creation timestamp, RFC3339. format: date-time type: string description: description: Human-readable summary of what this event represents. type: string event_schema: additionalProperties: {} description: JSON Schema payloads are softly validated against, if one is registered. type: object metadata: additionalProperties: type: string description: Arbitrary key/value metadata. type: object name: description: Event type name, e.g. order.created. type: string sample_payload: additionalProperties: {} description: Example payload derived from the schema, used to preview subscription transform templates. type: object updated_at: description: Last-modified timestamp, RFC3339. format: date-time type: string version: description: Current schema version. Starts at 1 and increases only when the schema changes; every version is kept (see listEventTypeVersions). format: int64 type: integer required: - change - name - active - version - created_at - updated_at type: object ValidateEventPayloadInputBody: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/ValidateEventPayloadInputBody.json format: uri readOnly: true type: string payload: additionalProperties: {} description: Payload to validate against the event type's registered JSON Schema. type: object required: - payload type: object ValidateEventPayloadOutputBody: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/ValidateEventPayloadOutputBody.json format: uri readOnly: true type: string valid: description: Whether the payload matches the event type's JSON Schema. Always true when the event type has no schema registered. type: boolean warnings: description: Per-field validation errors, present when valid is false. items: type: string type: array required: - valid type: object Webhook: additionalProperties: false properties: headers: additionalProperties: type: string description: Static headers sent with every delivery; values may contain {{param}} tokens. type: object requires_transform: description: The destination only accepts the transformed payload; the webhook is registered with requires_transform. type: boolean secret_headers: additionalProperties: type: string description: Headers whose values are encrypted at rest and masked in API responses (upstream tokens, passwords); values may contain {{param}} tokens. type: object url: description: Destination URL; may contain {{param}} tokens. type: string required: - url type: object WebhookHTTPConfig: additionalProperties: false properties: capture_response_body: description: "How much of the endpoint's response body is stored with each attempt: false (default) keeps up to 1 KiB, true up to SPARROW_MAX_CAPTURED_RESPONSE_BYTES (default 1 MiB)." type: boolean content_type: description: "Stored and returned, but deliveries do not use it yet: they send Content-Type: application/json. To send another Content-Type, set it in headers (or the subscription's headers). Must not be empty; defaults to application/json." type: string expected_status_codes: description: HTTP status codes treated as a successful delivery, each between 100 and 599. Defaults to 200, 201, 202 and 204. items: format: int64 type: integer type: array follow_redirects: description: "Whether to follow HTTP redirects returned by the endpoint. Each redirect target passes the same SSRF checks, and a redirect that changes the scheme, host or port gets only Sparrow's own headers: headers and secret_headers are dropped. Defaults to true." type: boolean max_retries: description: Retries after the first attempt before a delivery is marked failed, so a delivery gets up to max_retries + 1 attempts. 0 means no retries. Between 0 and 20; defaults to 13, which with the default retry_backoff_seconds keeps retrying for about three days. format: int64 type: integer rate_limit_rps: description: Maximum delivery rate to this webhook, in requests per second (fractions allowed, e.g. 0.5). Must be positive; unset means no limit. Deliveries over the limit wait in the queue. format: double type: number request_timeout_seconds: description: How long to wait for the endpoint to respond before treating the attempt as a timeout, in seconds. Between 1 and 300; defaults to 30. format: int64 type: integer retry_backoff_seconds: description: Base retry delay, in seconds. The delay after attempt N is retry_backoff_seconds * 2^(N-1), capped at 24 hours, minus a random 0-20% so retries spread out. Between 1 and 3600; defaults to 60. The defaults give retries after about 1, 2, 4, 8, 16, 32, 64, 128, 256, 512 and 1024 minutes, then 24 hours twice. format: int64 type: integer user_agent: description: "Stored and returned, but deliveries do not use it yet: they always send User-Agent: Sparrow-Webhook/. To send another User-Agent, set it in headers. Defaults to Sparrow-Webhook/1.0." type: string verify_ssl: description: Whether to verify the endpoint's TLS certificate. Defaults to true. Set false only for trusted internal endpoints with self-signed certificates; SSRF and redirect checks still apply. type: boolean type: object WebhookHTTPConfigOut: additionalProperties: false properties: capture_response_body: description: "false: up to 1 KiB of each response body is stored; true: up to SPARROW_MAX_CAPTURED_RESPONSE_BYTES (default 1 MiB)." type: boolean content_type: description: "Stored value; deliveries currently send Content-Type: application/json unless headers override it." type: string expected_status_codes: description: HTTP status codes treated as a successful delivery. items: format: int32 type: integer type: array follow_redirects: description: Whether HTTP redirects returned by the endpoint are followed. type: boolean max_retries: description: Retries after the first attempt before a delivery is marked failed (up to max_retries + 1 attempts). format: int64 type: integer rate_limit_rps: description: Delivery rate limit for this webhook, in requests per second; absent means no limit. format: double type: number request_timeout_seconds: description: Per-attempt request timeout, in seconds. format: int64 type: integer retry_backoff_seconds: description: Base retry delay, in seconds; the delay after attempt N is retry_backoff_seconds * 2^(N-1), capped at 24 hours, minus a random 0-20%. format: int64 type: integer user_agent: description: "Stored value; deliveries currently always send User-Agent: Sparrow-Webhook/ unless headers override it." type: string verify_ssl: description: Whether the endpoint's TLS certificate is verified. type: boolean webhook_secret: description: HMAC signing secret. Shown in full only once, in the response to webhook registration; masked on every later read. type: string required: - max_retries - retry_backoff_seconds - capture_response_body - follow_redirects - verify_ssl - request_timeout_seconds type: object WebhookHealthOutputBody: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/WebhookHealthOutputBody.json format: uri readOnly: true type: string avg_response_time: description: Average endpoint response time over the window, in milliseconds. format: int64 type: integer client_errors: description: Failed attempts in the window with a 4xx response (client_error). format: int64 type: integer consecutive_failures: description: Current run of consecutive failed attempts; resets on a success. format: int64 type: integer failed_deliveries: description: Failed attempts in the window. format: int64 type: integer health: description: "Health label from the last health evaluation: healthy, degraded, unhealthy, or unknown (too few recent attempts). Updated every SPARROW_HEALTH_EVAL_INTERVAL (default 1m)." enum: - healthy - degraded - unhealthy - unknown type: string network_errors: description: Failed attempts in the window with a network-level error (DNS, TLS, connection refused, reset). format: int64 type: integer rules: $ref: "#/components/schemas/HealthRules" description: Thresholds the health status is derived from. server_errors: description: Failed attempts in the window with a 5xx response (server_error). format: int64 type: integer success_rate: description: successful_deliveries / total_deliveries over the window, 0.0 to 1.0. format: double type: number successful_deliveries: description: Successful attempts in the window. format: int64 type: integer timeout_errors: description: Failed attempts in the window that timed out. format: int64 type: integer total_deliveries: description: Delivery attempts in the last rules.window_hours hours (each retry counts). format: int64 type: integer unexpected_status_errors: description: Attempts in the window whose 2xx/3xx response was not in expected_status_codes. format: int64 type: integer webhook_id: description: Webhook id (UUID). type: string required: - webhook_id - health - total_deliveries - successful_deliveries - failed_deliveries - consecutive_failures - success_rate - avg_response_time - client_errors - server_errors - timeout_errors - network_errors - unexpected_status_errors - rules type: object WebhookOut: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/WebhookOut.json format: uri readOnly: true type: string active: description: "Whether deliveries are sent. false: paused by an operator or auto-disabled; new deliveries are held as paused." type: boolean auto_disabled_at: description: Set when Sparrow paused this webhook itself because its receiver kept failing (see SPARROW_AUTO_DISABLE_AFTER), RFC3339. Cleared when the webhook is resumed. format: date-time type: string auto_disabled_reason: description: Why Sparrow auto-disabled this webhook. Cleared when the webhook is resumed. type: string consumer: description: Consumer this webhook belongs to. type: string created_at: description: Creation timestamp, RFC3339. format: date-time type: string description: description: Human-readable note about this webhook. type: string events: description: Event type names this webhook is currently subscribed to, derived from its subscriptions. items: type: string type: array headers: additionalProperties: type: string description: Static HTTP headers sent with every delivery. type: object health: description: Health label from the last health evaluation (see getWebhookHealth). enum: - healthy - degraded - unhealthy - unknown type: string http_config: $ref: "#/components/schemas/WebhookHTTPConfigOut" description: Per-webhook HTTP delivery configuration. requires_transform: description: Whether every subscription must carry an enabled transform_template because the receiver only accepts transformed payloads. type: boolean secret_headers: additionalProperties: type: string description: Encrypted secret header names, with values always masked. Update them via PATCH secret_headers; send an empty string to remove one. type: object signature_type: description: Signing scheme. "hmac" (default) signs every delivery with HMAC-SHA256 (v1,); "ed25519" adds an Ed25519 signature (v1a,) alongside the HMAC one. enum: - hmac - ed25519 type: string signing_public_key: description: Hex-encoded Ed25519 public key for verifying the v1a, delivery signature. Safe to expose; there is no private-key equivalent to mask. type: string updated_at: description: Last-modified timestamp, RFC3339. format: date-time type: string url: description: HTTP endpoint deliveries are POSTed to. type: string webhook_id: description: Webhook id (UUID). type: string required: - webhook_id - consumer - events - url - active - health - signature_type - http_config - requires_transform - created_at - updated_at type: object WhoAmIOutputBody: additionalProperties: false properties: $schema: description: A URL to the JSON Schema for this object. examples: - https://example.com/schemas/WhoAmIOutputBody.json format: uri readOnly: true type: string auth_enabled: description: "False when SPARROW_API_KEY is unset: every endpoint is open." type: boolean master_key: description: True when the request used SPARROW_API_KEY itself. Always false when auth is off. type: boolean name: description: "Name of the credential used for this request: the master key's name, the access token's name, or a placeholder when auth is off." type: string token_id: description: Id of the access token used; null for the master key or when auth is off. type: - string - "null" required: - auth_enabled - name - master_key - token_id type: object securitySchemes: ApiKeyAuth: description: "The master key (SPARROW_API_KEY) or a tenant-wide access token. Also accepted as Authorization: Bearer . Required only when the server is started with SPARROW_API_KEY set; otherwise all endpoints are open. The embedded UI never receives the key: it asks for a credential on the first 401." in: header name: X-API-Key type: apiKey info: contact: name: Sparrow url: https://github.com/sarathsp06/sparrow description: |- Sparrow is a self-hosted webhook delivery platform: register the events your system produces, register the webhooks that should receive them, then push event occurrences and Sparrow fans them out with retries and health tracking. ## Concepts - **Event type** (`Event Types`) — a named, versioned schema for something that happened in your system, e.g. `order.created`. Optional JSON Schema validation is *soft*: invalid payloads are stored with warnings, never rejected. - **Event** (`Events`) — one occurrence of an event type, pushed with a payload. Pushing an event asynchronously fans it out to every matching subscription. - **Webhook** (`Webhooks`) — a registered HTTP endpoint plus its delivery configuration (retries, timeouts, signing). Every delivery is signed with HMAC-SHA256 (Standard Webhooks), plus Ed25519 when the webhook's signature_type is ed25519. - **Subscription** (`Subscriptions`) — links one webhook to one event type within a consumer, optionally with a Go-template payload transform and label filters. Registering a webhook auto-creates matching subscriptions. - **Delivery** (`Deliveries`) — one attempt (and its retry history) to send an event to a webhook. Failures are classified as retryable (5xx, timeout, network) or not (4xx, DNS, TLS). - **Health** (`Health`) — a per-webhook label (healthy/degraded/unhealthy/unknown) derived from the last 24 hours of attempts, refreshed every `SPARROW_HEALTH_EVAL_INTERVAL`. ## Consumers Webhooks, events, subscriptions, and deliveries are scoped to a `consumer` path segment (defaults to `default` if you don't need multi-tenancy). A handful of read endpoints are deliberately global (no consumer) for cross-tenant dashboards and id-only lookups where the resource id is already globally unique. ## Authentication Optional. When the server is started with `SPARROW_API_KEY` set, every `/v1/*` request must carry that master key or a tenant-wide access token (`createToken` without a `consumer`), in the `X-API-Key` header or as `Authorization: Bearer `. When `SPARROW_API_KEY` is unset, every endpoint is open, and outside `SPARROW_ENVIRONMENT=production` CORS also allows any origin: never expose such a server beyond a trusted network. ## Portal tokens A consumer-scoped access token (`createToken` with a `consumer`) powers the embedded portal at `/portal`. It is refused on `/v1` (403); its API calls go to the single `/portal/api/` prefix with `Authorization: Bearer `, and the consumer is carried by the token, not the URL, so exposing the portal means allowlisting only `/portal`, `/_app`, and `/portal/api`. `/portal/api/` is served by `/v1/consumers//`, except pushing or re-pushing events (`POST events`, `POST events:rePush`, `repush-jobs`, `prepare_repush`: refused), plus these global helpers: `GET event-types[/...]` (listed for the token's consumer), `GET template-functions`, `GET capabilities`, and `POST subscriptions:testTemplate`. With `SPARROW_PORTAL_RETRY=false` the delivery retry calls and `prepare_retry` are refused too (403), and `GET capabilities` reports `portal.retry: false`. Each operation's description says whether portal tokens can call it. ## Errors Errors from the API itself are RFC 9457 problem details (`Content-Type: application/problem+json`): `{"title", "status", "detail", "errors"}`, where `errors` lists per-field validation problems. Two layers in front of the API answer with plain `application/json` instead: - Authentication (`401`, `503`): `{"error": "unauthorized", "reason": "missing|invalid|expired|revoked", "message": "..."}`; `reason` is absent for `503 unavailable` (retry after the `Retry-After` header). - Authorization and the portal gateway (`401`, `403`): `{"error": "forbidden" or "unauthorized", "message": "..."}`, e.g. a consumer-scoped token on `/v1`. Status codes: `400` invalid input, `404` not found, `409` conflict or wrong state, `413` body over `SPARROW_MAX_BODY_BYTES`, `429` rate limited, `500` internal (details are logged, not returned), `503` temporarily unavailable, `504` timed out. license: identifier: MIT name: MIT title: Sparrow version: 1.0.0 openapi: 3.1.0 paths: /v1/capabilities: get: description: |- Reports which optional, deployment-configured features this server offers so clients can show or hide the matching UI: AI-assisted transform template drafting, with the provider and model in use, whether alert email delivery is set up, and whether portal tokens may retry deliveries. Read-only. ai_drafting.enabled reflects SPARROW_AI_ENABLED=true; drafting is usable only when enabled is true and problem is absent; prompt_only is the opposite of enabled. Portal tokens: allowed, as GET /portal/api/capabilities. operationId: getCapabilities responses: "200": content: application/json: schema: $ref: "#/components/schemas/CapabilitiesOutputBody" description: OK default: content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Error summary: List optional server features tags: - Server /v1/consumers: get: description: |- Lists the consumers that own a webhook or an event, sorted by name, optionally only those whose name contains q (case-insensitive). Consumers are created implicitly by registering a webhook or pushing an event under them; this is how clients find them, e.g. for a consumer picker. Not paginated: returns at most limit names. Portal tokens: not allowed. operationId: listConsumers parameters: - description: Only consumers whose name contains this text, case-insensitive. explode: false in: query name: q schema: description: Only consumers whose name contains this text, case-insensitive. maxLength: 255 type: string - description: Maximum names to return. explode: false in: query name: limit schema: default: 50 description: Maximum names to return. format: int32 maximum: 1000 minimum: 1 type: integer responses: "200": content: application/json: schema: $ref: "#/components/schemas/ListConsumersOutputBody" description: OK default: content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Error summary: Search consumer names tags: - Webhooks /v1/consumers/{consumer}/alert-configs: get: description: |- Lists alert configs in a consumer, oldest first, optionally filtered to one webhook's own configs. Not paginated. Portal tokens: allowed, as GET /portal/api/alert-configs. operationId: listAlertConfigs parameters: - description: Consumer to list alert configs in. in: path name: consumer required: true schema: description: Consumer to list alert configs in. type: string - description: Filter to alert configs scoped to one webhook. Omit to list every config in the consumer, including consumer-wide ones. explode: false in: query name: webhook_id schema: description: Filter to alert configs scoped to one webhook. Omit to list every config in the consumer, including consumer-wide ones. type: string responses: "200": content: application/json: schema: $ref: "#/components/schemas/ListAlertConfigsOutputBody" description: OK default: content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Error summary: List email alert recipients tags: - Alert Configs post: description: |- Opts an email address into Sparrow's self-generated health-change, permanent-delivery-failure and auto-disable notifications, scoped to one webhook or the whole consumer. Emails are only sent once an alert mailer webhook is registered under the _sparrow consumer (see GET /v1/capabilities alert_delivery.configured). Not idempotent: each call adds a config. 400 for an invalid email or event type, 404 when webhook_id is not in this consumer. Portal tokens: allowed, as POST /portal/api/alert-configs. operationId: createAlertConfig parameters: - description: Consumer whose webhooks the alert covers. in: path name: consumer required: true schema: description: Consumer whose webhooks the alert covers. type: string requestBody: content: application/json: schema: $ref: "#/components/schemas/CreateAlertConfigBody" required: true responses: "201": content: application/json: schema: $ref: "#/components/schemas/AlertConfigItem" description: Created "400": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Bad Request "404": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Not Found "422": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Unprocessable Entity "500": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Internal Server Error summary: Register an email alert recipient tags: - Alert Configs /v1/consumers/{consumer}/alert-configs/{alert_config_id}: delete: description: |- Removes an alert config. The webhook and its delivery history are unaffected. Returns 204; 404 when it is not in this consumer. Portal tokens: allowed, as DELETE /portal/api/alert-configs/{alert_config_id}. operationId: deleteAlertConfig parameters: - description: Consumer the alert config belongs to. in: path name: consumer required: true schema: description: Consumer the alert config belongs to. type: string - description: Alert config id (UUID). in: path name: alert_config_id required: true schema: description: Alert config id (UUID). type: string responses: "204": description: No Content "404": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Not Found "422": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Unprocessable Entity "500": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Internal Server Error summary: Delete an email alert recipient tags: - Alert Configs /v1/consumers/{consumer}/deliveries: get: description: |- Lists deliveries in a consumer, newest first, optionally filtered by webhook, event occurrence, subscription, event type, status, error category, or creation time. Cursor paginated: pass pagination.next_cursor as cursor for the next page; there is no total count and offset is rejected. Side effect: prepare_retry=true also snapshots every match (up to 10,000) into a batch job and returns retry_id for POST .../deliveries:retryBatch. Portal tokens: allowed, as GET /portal/api/deliveries; prepare_retry only while SPARROW_PORTAL_RETRY is true (otherwise 403). operationId: listDeliveries parameters: - description: Consumer to list deliveries in. in: path name: consumer required: true schema: description: Consumer to list deliveries in. type: string - description: Filter to deliveries for one webhook (UUID; 400 otherwise). explode: false in: query name: webhook_id schema: description: Filter to deliveries for one webhook (UUID; 400 otherwise). type: string - description: Filter to deliveries for one pushed event occurrence (UUID; 400 otherwise). explode: false in: query name: event_id schema: description: Filter to deliveries for one pushed event occurrence (UUID; 400 otherwise). type: string - description: Filter by delivery status. explode: false in: query name: status schema: description: Filter by delivery status. enum: - pending - sending - success - failed - retrying - expired - paused type: string - description: Filter by the most recent attempt's error_category, e.g. server_error, client_error, timeout. explode: false in: query name: error_category schema: description: Filter by the most recent attempt's error_category, e.g. server_error, client_error, timeout. type: string - description: Filter to deliveries created by one subscription (UUID), e.g. its paused deliveries. explode: false in: query name: subscription_id schema: description: Filter to deliveries created by one subscription (UUID), e.g. its paused deliveries. type: string - description: Filter to deliveries of one event type, e.g. order.created. Also matches deliveries whose subscription has since been replaced or deleted. explode: false in: query name: event_name schema: description: Filter to deliveries of one event type, e.g. order.created. Also matches deliveries whose subscription has since been replaced or deleted. type: string - description: "Only deliveries created at or after this time: YYYY-MM-DD (start of that day, UTC) or an RFC3339 timestamp (e.g. an import's imported_at)." explode: false in: query name: created_after schema: description: "Only deliveries created at or after this time: YYYY-MM-DD (start of that day, UTC) or an RFC3339 timestamp (e.g. an import's imported_at)." type: string - description: "Only deliveries created at or before this time: YYYY-MM-DD (through the end of that day, UTC) or an RFC3339 timestamp." explode: false in: query name: created_before schema: description: "Only deliveries created at or before this time: YYYY-MM-DD (through the end of that day, UTC) or an RFC3339 timestamp." type: string - description: "Side effect: when true, also snapshots the ids of every delivery matching the filters (not just this page; at most 10,000, more is an error) into a batch job and returns its id as retry_id. Pass it to POST .../deliveries:retryBatch within 15 minutes. Each call creates a new snapshot." explode: false in: query name: prepare_retry schema: default: false description: "Side effect: when true, also snapshots the ids of every delivery matching the filters (not just this page; at most 10,000, more is an error) into a batch job and returns its id as retry_id. Pass it to POST .../deliveries:retryBatch within 15 minutes. Each call creates a new snapshot." type: boolean - description: Maximum items to return, 1 to 1000. explode: false in: query name: limit schema: default: 50 description: Maximum items to return, 1 to 1000. format: int32 maximum: 1000 minimum: 1 type: integer - description: "Continue after the page this came from: pagination.next_cursor of the previous response. Stable while new deliveries arrive." explode: false in: query name: cursor schema: description: "Continue after the page this came from: pagination.next_cursor of the previous response. Stable while new deliveries arrive." type: string - description: "Removed: page with cursor. Any value other than 0 is rejected with 400, so a client still paging by offset fails loudly instead of re-reading the first page." explode: false in: query name: offset schema: default: 0 description: "Removed: page with cursor. Any value other than 0 is rejected with 400, so a client still paging by offset fails loudly instead of re-reading the first page." format: int32 type: integer responses: "200": content: application/json: schema: $ref: "#/components/schemas/ListDeliveriesOutputBody" description: OK default: content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Error summary: List deliveries tags: - Deliveries /v1/consumers/{consumer}/deliveries/{delivery_id}: get: description: |- Returns one delivery's status, latest response code/body, and error classification. 404 when it does not exist in this consumer. Portal tokens: allowed, as GET /portal/api/deliveries/{delivery_id}. operationId: getDelivery parameters: - description: Consumer the delivery belongs to. in: path name: consumer required: true schema: description: Consumer the delivery belongs to. type: string - description: Delivery id (UUID). in: path name: delivery_id required: true schema: description: Delivery id (UUID). type: string responses: "200": content: application/json: schema: $ref: "#/components/schemas/DeliveryItem" description: OK "404": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Not Found "422": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Unprocessable Entity "500": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Internal Server Error summary: Get a delivery by id tags: - Deliveries /v1/consumers/{consumer}/deliveries/{delivery_id}/attempts: get: description: |- Returns the per-attempt record for one delivery, oldest first: response code, timing, and error classification for every attempt made so far, across manual retries. A delivery that does not exist or belongs to another consumer answers 404. Portal tokens: allowed, as GET /portal/api/deliveries/{delivery_id}/attempts. operationId: getDeliveryAttempts parameters: - description: Consumer the delivery belongs to. in: path name: consumer required: true schema: description: Consumer the delivery belongs to. type: string - description: Delivery id (UUID). in: path name: delivery_id required: true schema: description: Delivery id (UUID). type: string responses: "200": content: application/json: schema: $ref: "#/components/schemas/AttemptsOutputBody" description: OK "404": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Not Found "422": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Unprocessable Entity "500": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Internal Server Error summary: Get a delivery's per-attempt history tags: - Deliveries /v1/consumers/{consumer}/deliveries/{delivery_id}:retry: post: description: |- Queues one delivery for another round of attempts right away: it is reset to pending and gets up to max_attempts new attempts, without TTL expiry. Works for failed, expired, paused and retrying deliveries: a retrying delivery's scheduled retry is cancelled and replaced, so it is sent now rather than after its backoff. Refused with 409 for a successful delivery (use the bulk route with force to resend it) and for one that is pending or being sent right now (retry it once that attempt finishes). If the webhook or subscription is still paused the delivery is held as paused again. Not idempotent: each call queues another round. Portal tokens: allowed, as POST /portal/api/deliveries/{delivery_id}:retry, unless the server runs with SPARROW_PORTAL_RETRY=false (then 403). operationId: retryDelivery parameters: - description: Consumer the delivery belongs to. in: path name: consumer required: true schema: description: Consumer the delivery belongs to. type: string - description: Delivery id (UUID) to retry. in: path name: delivery_id required: true schema: description: Delivery id (UUID) to retry. type: string responses: "200": content: application/json: schema: $ref: "#/components/schemas/RetryOutputBody" description: OK "400": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Bad Request "404": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Not Found "409": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Conflict "422": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Unprocessable Entity "500": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Internal Server Error summary: Retry a single delivery tags: - Deliveries /v1/consumers/{consumer}/deliveries:retry: post: description: |- Queues every failed or paused delivery of one webhook for another round of attempts (see retryDelivery), in one request. With force, every delivery of the webhook is resent, including successful, expired and retrying ones (a retrying one's scheduled retry is replaced); deliveries pending or being sent right now are skipped. Returns count 0 when nothing matched. For a filtered or very large set, use prepare_retry and :retryBatch instead. 404 when the webhook does not exist in this consumer. Portal tokens: allowed, as POST /portal/api/deliveries:retry, unless the server runs with SPARROW_PORTAL_RETRY=false (then 403). operationId: retryDeliveriesByWebhook parameters: - description: Consumer the webhook belongs to. in: path name: consumer required: true schema: description: Consumer the webhook belongs to. type: string requestBody: content: application/json: schema: $ref: "#/components/schemas/RetryDeliveriesByWebhookInputBody" required: true responses: "200": content: application/json: schema: $ref: "#/components/schemas/RetryOutputBody" description: OK "400": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Bad Request "404": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Not Found "422": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Unprocessable Entity "500": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Internal Server Error summary: Retry deliveries in bulk for a webhook tags: - Deliveries /v1/consumers/{consumer}/deliveries:retryBatch: post: description: |- Starts an async job that retries every delivery in a snapshot taken by an earlier GET .../deliveries?prepare_retry=true call, like retryDelivery for each one. Send the retry_id the deliveries list returned. Returns 202 with the job; poll it with getDeliveryRetryJob. A snapshot can be started once: 409 when it is not pending (already started or cancelled), expired (15 minutes after it was taken), or is an event re-push snapshot; 404 when the id is unknown, 400 when it is not a UUID. Portal tokens: allowed, as POST /portal/api/deliveries:retryBatch, unless the server runs with SPARROW_PORTAL_RETRY=false (then 403). operationId: startDeliveryRetryJob parameters: - description: Consumer the job belongs to. A job snapshotted for another consumer answers 404; a job snapshotted across all consumers is reachable under any consumer, except with a portal token. in: path name: consumer required: true schema: description: Consumer the job belongs to. A job snapshotted for another consumer answers 404; a job snapshotted across all consumers is reachable under any consumer, except with a portal token. type: string requestBody: content: application/json: schema: $ref: "#/components/schemas/RetryBatchInputBody" required: true responses: "202": content: application/json: schema: $ref: "#/components/schemas/BatchJobOutputBody" description: Accepted "400": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Bad Request "404": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Not Found "409": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Conflict "422": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Unprocessable Entity "500": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Internal Server Error summary: Start a batch retry job from a prepared snapshot tags: - Deliveries /v1/consumers/{consumer}/events: get: description: |- Lists pushed event occurrences in a consumer, newest first, with delivery outcome counts per occurrence. Cursor paginated: pass pagination.next_cursor as cursor for the next page; there is no total count and offset is rejected. Side effect: prepare_repush=true also snapshots every match (up to 10,000) into a batch job and returns repush_id for POST .../events:rePush. Portal tokens: allowed, as GET /portal/api/events, without prepare_repush (re-push is not a portal action; 403). operationId: listEventOccurrences parameters: - description: Consumer to list occurrences in. in: path name: consumer required: true schema: description: Consumer to list occurrences in. type: string - description: Filter to occurrences of this event type name. explode: false in: query name: event schema: description: Filter to occurrences of this event type name. type: string - description: "Filter by schema validation result: valid or invalid. Other values are 400." explode: false in: query name: schema_valid schema: description: "Filter by schema validation result: valid or invalid. Other values are 400." type: string - description: Filter by labels as comma-separated key=value pairs, e.g. region=eu,tier=gold. Matches occurrences whose labels contain every pair. explode: false in: query name: labels schema: description: Filter by labels as comma-separated key=value pairs, e.g. region=eu,tier=gold. Matches occurrences whose labels contain every pair. type: string - description: "Only occurrences created at or after this time: YYYY-MM-DD (start of that day, UTC) or an RFC3339 timestamp." explode: false in: query name: created_after schema: description: "Only occurrences created at or after this time: YYYY-MM-DD (start of that day, UTC) or an RFC3339 timestamp." type: string - description: "Only occurrences created at or before this time: YYYY-MM-DD (through the end of that day, UTC) or an RFC3339 timestamp." explode: false in: query name: created_before schema: description: "Only occurrences created at or before this time: YYYY-MM-DD (through the end of that day, UTC) or an RFC3339 timestamp." type: string - description: "Side effect: when true, also snapshots the ids of every occurrence matching the filters (not just this page; at most 10,000, more is an error) into a batch job and returns its id as repush_id. Pass it to POST .../events:rePush within 15 minutes. Each call creates a new snapshot." explode: false in: query name: prepare_repush schema: default: false description: "Side effect: when true, also snapshots the ids of every occurrence matching the filters (not just this page; at most 10,000, more is an error) into a batch job and returns its id as repush_id. Pass it to POST .../events:rePush within 15 minutes. Each call creates a new snapshot." type: boolean - description: Maximum items to return, 1 to 1000. explode: false in: query name: limit schema: default: 50 description: Maximum items to return, 1 to 1000. format: int32 maximum: 1000 minimum: 1 type: integer - description: "Continue after the page this came from: pagination.next_cursor of the previous response. Stable while new events arrive." explode: false in: query name: cursor schema: description: "Continue after the page this came from: pagination.next_cursor of the previous response. Stable while new events arrive." type: string - description: "Removed: page with cursor. Any value other than 0 is rejected with 400, so a client still paging by offset fails loudly instead of re-reading the first page." explode: false in: query name: offset schema: default: 0 description: "Removed: page with cursor. Any value other than 0 is rejected with 400, so a client still paging by offset fails loudly instead of re-reading the first page." format: int32 type: integer responses: "200": content: application/json: schema: $ref: "#/components/schemas/ListEventOccurrencesOutputBody" description: OK default: content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Error summary: List pushed event occurrences tags: - Events post: description: |- Records one occurrence of a registered event type and asynchronously fans it out to every subscription whose event name and label filters match. Returns 201 as soon as the event is stored and queued; delivery happens in the background, so check Deliveries for outcomes. A payload that fails the event type's JSON Schema is still accepted (schema_valid: false, with warnings). Idempotency: with idempotency_key, a repeat push with the same key in the same consumer returns 201 with duplicate: true and the original event_id, and creates nothing. Without a key every call creates a new event. Scheduling: with scheduled_at, the occurrence is stored immediately but fans out only at that time; until then it lists with no deliveries. A past time means immediate; more than one year ahead is 400. Failures: 404 for an unregistered event type (unless SPARROW_AUTO_REGISTER_EVENTS=true), 409 for an inactive one, 400 for a reserved sparrow.* name, a name over 255 characters, invalid labels or a scheduled_at over a year ahead, 413 for a body over SPARROW_MAX_BODY_BYTES. Portal tokens: not allowed (pushing is the producer's job). operationId: pushEvent parameters: - description: Consumer to record the occurrence in. Created implicitly on first use. in: path name: consumer required: true schema: description: Consumer to record the occurrence in. Created implicitly on first use. type: string - description: Name of a registered, active event type (at most 255 characters). An unregistered name is 404 unless SPARROW_AUTO_REGISTER_EVENTS=true, which registers it if it follows the event type naming rule (lowercase letters, digits, '_' and '-' in dot-separated parts; 400 otherwise); sparrow.* names are reserved (400). explode: false in: query name: event required: true schema: description: Name of a registered, active event type (at most 255 characters). An unregistered name is 404 unless SPARROW_AUTO_REGISTER_EVENTS=true, which registers it if it follows the event type naming rule (lowercase letters, digits, '_' and '-' in dot-separated parts; 400 otherwise); sparrow.* names are reserved (400). type: string requestBody: content: application/json: schema: $ref: "#/components/schemas/PushEventBody" required: true responses: "201": content: application/json: schema: $ref: "#/components/schemas/PushEventOutputBody" description: Created "400": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Bad Request "404": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Not Found "409": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Conflict "413": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Request Entity Too Large "422": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Unprocessable Entity "500": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Internal Server Error summary: Push an event occurrence for asynchronous delivery tags: - Events /v1/consumers/{consumer}/events:rePush: post: description: |- Starts an async job that re-pushes every event occurrence in a snapshot taken by an earlier GET .../events?prepare_repush=true call. Each re-push creates a new event occurrence with the original payload, without re-validating it; sparrow.* system events are skipped and counted as failed. Returns 202 with the job; poll it with getEventRepushJob. A snapshot can be started once: 409 when it is not pending (already started or cancelled) or expired (15 minutes after it was taken), 404 when the id is unknown, 400 when it is not a UUID. Portal tokens: not allowed (re-pushing events is the producer's job, like pushing them). operationId: startEventRepushJob parameters: - description: Consumer the job belongs to. A job snapshotted for another consumer answers 404; a job snapshotted across all consumers is reachable under any consumer, except with a portal token. in: path name: consumer required: true schema: description: Consumer the job belongs to. A job snapshotted for another consumer answers 404; a job snapshotted across all consumers is reachable under any consumer, except with a portal token. type: string requestBody: content: application/json: schema: $ref: "#/components/schemas/RepushBatchInputBody" required: true responses: "202": content: application/json: schema: $ref: "#/components/schemas/BatchJobOutputBody" description: Accepted "400": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Bad Request "404": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Not Found "409": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Conflict "422": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Unprocessable Entity "500": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Internal Server Error summary: Start a batch re-push job from a prepared snapshot tags: - Events /v1/consumers/{consumer}/repush-jobs/{job_id}: get: description: |- Returns a batch re-push job's status and processed/failed/total counts. 404 when the id is unknown, 409 when the id belongs to a delivery retry job. Portal tokens: not allowed. operationId: getEventRepushJob parameters: - description: Consumer the job belongs to. A job snapshotted for another consumer answers 404; a job snapshotted across all consumers is reachable under any consumer, except with a portal token. in: path name: consumer required: true schema: description: Consumer the job belongs to. A job snapshotted for another consumer answers 404; a job snapshotted across all consumers is reachable under any consumer, except with a portal token. type: string - description: "Batch job id (UUID): the repush_id or retry_id from the list call." in: path name: job_id required: true schema: description: "Batch job id (UUID): the repush_id or retry_id from the list call." type: string responses: "200": content: application/json: schema: $ref: "#/components/schemas/BatchJobOutputBody" description: OK "400": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Bad Request "404": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Not Found "409": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Conflict "422": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Unprocessable Entity "500": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Internal Server Error summary: Get batch re-push job progress tags: - Events /v1/consumers/{consumer}/repush-jobs/{job_id}:cancel: post: description: |- Cancels a pending or running batch re-push job; a running job stops at its next progress check (every 25 items). Occurrences already re-pushed are not rolled back. Returns 204. 409 when the job is already completed or cancelled. Portal tokens: not allowed. operationId: cancelEventRepushJob parameters: - description: Consumer the job belongs to. A job snapshotted for another consumer answers 404; a job snapshotted across all consumers is reachable under any consumer, except with a portal token. in: path name: consumer required: true schema: description: Consumer the job belongs to. A job snapshotted for another consumer answers 404; a job snapshotted across all consumers is reachable under any consumer, except with a portal token. type: string - description: "Batch job id (UUID): the repush_id or retry_id from the list call." in: path name: job_id required: true schema: description: "Batch job id (UUID): the repush_id or retry_id from the list call." type: string responses: "204": description: No Content "400": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Bad Request "404": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Not Found "409": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Conflict "422": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Unprocessable Entity "500": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Internal Server Error summary: Cancel a pending or in-progress batch re-push job tags: - Events /v1/consumers/{consumer}/retry-jobs/{job_id}: get: description: |- Returns a batch retry job's status and processed/failed/total counts. 404 when the id is unknown, 409 when the id belongs to an event re-push job. Portal tokens: allowed, as GET /portal/api/retry-jobs/{job_id}. operationId: getDeliveryRetryJob parameters: - description: Consumer the job belongs to. A job snapshotted for another consumer answers 404; a job snapshotted across all consumers is reachable under any consumer, except with a portal token. in: path name: consumer required: true schema: description: Consumer the job belongs to. A job snapshotted for another consumer answers 404; a job snapshotted across all consumers is reachable under any consumer, except with a portal token. type: string - description: "Batch job id (UUID): the repush_id or retry_id from the list call." in: path name: job_id required: true schema: description: "Batch job id (UUID): the repush_id or retry_id from the list call." type: string responses: "200": content: application/json: schema: $ref: "#/components/schemas/BatchJobOutputBody" description: OK "400": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Bad Request "404": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Not Found "409": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Conflict "422": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Unprocessable Entity "500": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Internal Server Error summary: Get batch retry job progress tags: - Deliveries /v1/consumers/{consumer}/retry-jobs/{job_id}:cancel: post: description: |- Cancels a pending or running batch retry job; a running job stops at its next progress check (every 25 items). Deliveries already queued are not rolled back. Returns 204. 409 when the job is already completed or cancelled. Portal tokens: allowed, as POST /portal/api/retry-jobs/{job_id}:cancel. operationId: cancelDeliveryRetryJob parameters: - description: Consumer the job belongs to. A job snapshotted for another consumer answers 404; a job snapshotted across all consumers is reachable under any consumer, except with a portal token. in: path name: consumer required: true schema: description: Consumer the job belongs to. A job snapshotted for another consumer answers 404; a job snapshotted across all consumers is reachable under any consumer, except with a portal token. type: string - description: "Batch job id (UUID): the repush_id or retry_id from the list call." in: path name: job_id required: true schema: description: "Batch job id (UUID): the repush_id or retry_id from the list call." type: string responses: "204": description: No Content "400": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Bad Request "404": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Not Found "409": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Conflict "422": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Unprocessable Entity "500": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Internal Server Error summary: Cancel a pending or in-progress batch retry job tags: - Deliveries /v1/consumers/{consumer}/stats: get: description: |- Returns webhook and delivery-attempt counts (total, active, successful, failed, pending, success rate) for one consumer. Attempt counts come from the health evaluator and lag by up to SPARROW_HEALTH_EVAL_INTERVAL; pending is live. Portal tokens: allowed, as GET /portal/api/stats. operationId: getConsumerStats parameters: - description: Consumer name. in: path name: consumer required: true schema: description: Consumer name. type: string responses: "200": content: application/json: schema: $ref: "#/components/schemas/ConsumerStatsOutputBody" description: OK default: content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Error summary: Get aggregate delivery statistics for a consumer tags: - Webhooks /v1/consumers/{consumer}/subscriptions: get: description: |- Lists subscriptions in a consumer, newest first, optionally filtered by webhook or event type name. Offset paginated with total_count. 404 when webhook_id is given and the webhook is not in this consumer. Portal tokens: allowed, as GET /portal/api/subscriptions. operationId: listSubscriptions parameters: - description: Consumer to list subscriptions in. in: path name: consumer required: true schema: description: Consumer to list subscriptions in. type: string - description: "Filter to subscriptions for one webhook (UUID). Takes precedence over event_name: when both are given, event_name is ignored." explode: false in: query name: webhook_id schema: description: "Filter to subscriptions for one webhook (UUID). Takes precedence over event_name: when both are given, event_name is ignored." type: string - description: Filter to subscriptions with exactly this event_name ("*" matches only catch-all subscriptions). explode: false in: query name: event_name schema: description: Filter to subscriptions with exactly this event_name ("*" matches only catch-all subscriptions). type: string - description: Maximum items to return, 1 to 1000. explode: false in: query name: limit schema: default: 50 description: Maximum items to return, 1 to 1000. format: int32 maximum: 1000 minimum: 1 type: integer - description: Number of items to skip, for pagination. explode: false in: query name: offset schema: default: 0 description: Number of items to skip, for pagination. format: int32 type: integer responses: "200": content: application/json: schema: $ref: "#/components/schemas/ListSubscriptionsOutputBody" description: OK default: content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Error summary: List subscriptions tags: - Subscriptions post: description: |- Subscribes a webhook to an event type within a consumer, with an optional payload transform and label filters. Registering a webhook with events already creates one subscription per name; use this for additional, catch-all ("*") or per-subscription-template subscriptions. A non-empty transform_template is recorded as the first template version. Not idempotent: 409 when the webhook already has a subscription for event_name. 404 when the webhook is not in this consumer. 400 for invalid headers, timeout or label filters, a sparrow.* event outside _sparrow, or a missing transform when the webhook has requires_transform. Portal tokens: allowed, as POST /portal/api/subscriptions. operationId: createSubscription parameters: - description: Consumer the webhook belongs to. in: path name: consumer required: true schema: description: Consumer the webhook belongs to. type: string requestBody: content: application/json: schema: $ref: "#/components/schemas/CreateSubscriptionBody" required: true responses: "201": content: application/json: schema: $ref: "#/components/schemas/SubscriptionItem" description: Created "400": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Bad Request "404": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Not Found "409": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Conflict "422": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Unprocessable Entity "500": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Internal Server Error summary: Create a subscription linking a webhook to an event tags: - Subscriptions /v1/consumers/{consumer}/subscriptions/{subscription_id}: delete: description: |- Deletes the subscription and its template history. The webhook stops receiving that event's occurrences; delivery history is kept. Cannot be undone. A second delete returns 404. Portal tokens: allowed, as DELETE /portal/api/subscriptions/{subscription_id}. operationId: deleteSubscription parameters: - description: Consumer the subscription belongs to. in: path name: consumer required: true schema: description: Consumer the subscription belongs to. type: string - description: Subscription id (UUID). in: path name: subscription_id required: true schema: description: Subscription id (UUID). type: string responses: "204": description: No Content "404": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Not Found "422": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Unprocessable Entity "500": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Internal Server Error summary: Delete a subscription tags: - Subscriptions get: description: |- Returns one subscription: its webhook link, transform template and settings, label filters and pause state. 404 when it is not in this consumer. Portal tokens: allowed, as GET /portal/api/subscriptions/{subscription_id}. operationId: getSubscription parameters: - description: Consumer the subscription belongs to. in: path name: consumer required: true schema: description: Consumer the subscription belongs to. type: string - description: Subscription id (UUID). in: path name: subscription_id required: true schema: description: Subscription id (UUID). type: string responses: "200": content: application/json: schema: $ref: "#/components/schemas/SubscriptionItem" description: OK "404": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Not Found "422": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Unprocessable Entity "500": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Internal Server Error summary: Get a subscription by id tags: - Subscriptions patch: description: |- Partially updates a subscription's headers, method, timeout, transform, or label filters: only fields present in the body change, and map fields replace the whole map. The linked webhook_id and event_name cannot be changed; delete and recreate instead. A changed transform_template is recorded as a new template version with template_source and template_notes. Returns the updated subscription. Idempotent: repeating the same body records no new version. 400 for invalid values, or when the webhook has requires_transform and the result has no enabled template. 404 when it is not in this consumer. Portal tokens: allowed, as PATCH /portal/api/subscriptions/{subscription_id}. operationId: updateSubscription parameters: - description: Consumer the subscription belongs to. in: path name: consumer required: true schema: description: Consumer the subscription belongs to. type: string - description: Subscription id (UUID). in: path name: subscription_id required: true schema: description: Subscription id (UUID). type: string requestBody: content: application/json: schema: $ref: "#/components/schemas/PatchSubscriptionBody" required: true responses: "200": content: application/json: schema: $ref: "#/components/schemas/SubscriptionItem" description: OK "400": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Bad Request "404": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Not Found "422": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Unprocessable Entity "500": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Internal Server Error summary: Partially update a subscription tags: - Subscriptions /v1/consumers/{consumer}/subscriptions/{subscription_id}/templateVersions: get: description: |- Returns the transform templates this subscription has been saved with, newest first; the newest is the current one. A version is recorded whenever a save changes the template, with how it was produced (manual or ai_draft), an optional note, and who saved it. The last 20 are kept. There is no restore endpoint: load a version into the editor and save the subscription, which records it as a new version. Portal tokens: allowed, as GET /portal/api/subscriptions/{subscription_id}/templateVersions. operationId: listSubscriptionTemplateVersions parameters: - description: Consumer the subscription belongs to. in: path name: consumer required: true schema: description: Consumer the subscription belongs to. type: string - description: Subscription id (UUID). in: path name: subscription_id required: true schema: description: Subscription id (UUID). type: string - description: Maximum versions to return, newest first, 1 to 20. explode: false in: query name: limit schema: default: 20 description: Maximum versions to return, newest first, 1 to 20. format: int64 maximum: 20 minimum: 1 type: integer responses: "200": content: application/json: schema: $ref: "#/components/schemas/ListTemplateVersionsOutputBody" description: OK "404": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Not Found "422": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Unprocessable Entity "500": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Internal Server Error summary: List a subscription's saved template history tags: - Subscriptions /v1/consumers/{consumer}/subscriptions/{subscription_id}:pause: post: description: |- Stops the subscription's deliveries from being attempted, for any reason (receiver maintenance, a template being fixed, an investigation). Events keep fanning out to it: each delivery is recorded with status paused and nothing is queued, so nothing is lost. Queued or retrying deliveries of the subscription are held as paused when their turn comes. A pause never affects the webhook's health. Returns the subscription (200), unlike webhook pause, which returns 204. Idempotent: pausing again updates the reason and keeps the original pause time. 400 for a reason over 500 characters, 404 when it is not in this consumer. Portal tokens: allowed, as POST /portal/api/subscriptions/{subscription_id}:pause. operationId: pauseSubscription parameters: - description: Consumer the subscription belongs to. in: path name: consumer required: true schema: description: Consumer the subscription belongs to. type: string - description: Subscription id (UUID). in: path name: subscription_id required: true schema: description: Subscription id (UUID). type: string requestBody: content: application/json: schema: $ref: "#/components/schemas/PauseSubscriptionInputBody" required: true responses: "200": content: application/json: schema: $ref: "#/components/schemas/SubscriptionItem" description: OK "400": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Bad Request "404": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Not Found "422": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Unprocessable Entity "500": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Internal Server Error summary: Pause a subscription tags: - Subscriptions /v1/consumers/{consumer}/subscriptions/{subscription_id}:resume: post: description: |- Lets new deliveries be attempted again. Deliveries recorded while paused stay paused and are never sent automatically: the response says how many there are and when the pause began, so you can retry all of them or only those since a time with the delivery retry endpoints. Idempotent: resuming a subscription that is not paused succeeds with no paused_since. Portal tokens: allowed, as POST /portal/api/subscriptions/{subscription_id}:resume. operationId: resumeSubscription parameters: - description: Consumer the subscription belongs to. in: path name: consumer required: true schema: description: Consumer the subscription belongs to. type: string - description: Subscription id (UUID). in: path name: subscription_id required: true schema: description: Subscription id (UUID). type: string responses: "200": content: application/json: schema: $ref: "#/components/schemas/ResumeSubscriptionOutputBody" description: OK "404": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Not Found "422": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Unprocessable Entity "500": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Internal Server Error summary: Resume a paused subscription tags: - Subscriptions /v1/consumers/{consumer}/subscriptions/{subscription_id}:sendTestEvent: post: description: |- Sends event_name to the subscription: one signed delivery of payload, or of the event type's sample when payload is omitted, rendered through the subscription's transform as a real delivery would be, to its webhook's URL, synchronously, and returns what the receiver answered. It uses the same request builder, signing, network policy (SSRF protection) and timeout as real deliveries and adds the header X-Sparrow-Test: true. It creates no delivery and never counts toward the webhook's health or auto-disable. A receiver error is reported in the response (status_code, error_class), not as an API error. One test event per webhook every 2 seconds on each server: more answer 429. 404 for a subscription not in this consumer. 400 when the subscription does not receive event_name, or when the event type is not registered and no payload is given. Portal tokens: allowed, as POST /portal/api/subscriptions/{subscription_id}:sendTestEvent. operationId: sendTestEvent parameters: - description: Tenant consumer the subscription belongs to. in: path name: consumer required: true schema: description: Tenant consumer the subscription belongs to. type: string - description: "Subscription to send the event to: its transform renders the payload, and its webhook receives it." in: path name: subscription_id required: true schema: description: "Subscription to send the event to: its transform renders the payload, and its webhook receives it." type: string requestBody: content: application/json: schema: $ref: "#/components/schemas/SendTestEventInputBody" required: true responses: "200": content: application/json: schema: $ref: "#/components/schemas/SendTestEventOutputBody" description: OK "400": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Bad Request "404": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Not Found "422": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Unprocessable Entity "429": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Too Many Requests "500": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Internal Server Error "503": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Service Unavailable summary: Send a test event to a subscription tags: - Subscriptions /v1/consumers/{consumer}/webhooks: get: description: |- Lists the webhooks of one consumer, newest first, optionally filtered by id, subscribed event, active flag, or health status. Secrets are masked. Offset paginated with total_count. Deleted webhooks are never listed. Portal tokens: allowed, as GET /portal/api/webhooks. operationId: listWebhooks parameters: - description: Tenant consumer to list webhooks in. in: path name: consumer required: true schema: description: Tenant consumer to list webhooks in. type: string - description: Filter to a single webhook by id. explode: false in: query name: webhook_id schema: description: Filter to a single webhook by id. type: string - description: Filter to webhooks subscribed to this event type name. explode: false in: query name: event schema: description: Filter to webhooks subscribed to this event type name. type: string - description: true returns only active (not paused) webhooks; false returns all. explode: false in: query name: active schema: default: false description: true returns only active (not paused) webhooks; false returns all. type: boolean - description: Filter by computed health status. explode: false in: query name: health schema: description: Filter by computed health status. enum: - healthy - degraded - unhealthy - unknown type: string - description: Maximum items to return, 1 to 1000. explode: false in: query name: limit schema: default: 50 description: Maximum items to return, 1 to 1000. format: int32 maximum: 1000 minimum: 1 type: integer - description: Number of items to skip, for pagination. explode: false in: query name: offset schema: default: 0 description: Number of items to skip, for pagination. format: int32 type: integer responses: "200": content: application/json: schema: $ref: "#/components/schemas/ListWebhooksOutputBody" description: OK default: content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Error summary: List webhooks tags: - Webhooks post: description: |- Registers a new HTTP endpoint and creates one subscription per name in events (with transform_template, when given). Generates the HMAC signing secret and returns it in plaintext in http_config.webhook_secret; this response is the only time it is shown. Event names are not required to be registered event types yet. Not idempotent: a second call with the same URL in the same consumer fails with 409 while the first webhook exists. 400 for an invalid URL (scheme, unresolvable or blocked host), invalid headers, http_config out of range, a sparrow.* event outside the _sparrow consumer, or requires_transform without transform_template. Portal tokens: allowed, as POST /portal/api/webhooks. operationId: registerWebhook parameters: - description: Consumer to register the webhook under. Created implicitly on first use. in: path name: consumer required: true schema: description: Consumer to register the webhook under. Created implicitly on first use. type: string requestBody: content: application/json: schema: $ref: "#/components/schemas/RegisterWebhookBody" required: true responses: "201": content: application/json: schema: $ref: "#/components/schemas/WebhookOut" description: Created "400": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Bad Request "409": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Conflict "422": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Unprocessable Entity "500": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Internal Server Error summary: Register a webhook tags: - Webhooks /v1/consumers/{consumer}/webhooks/{webhook_id}: delete: description: |- Deletes a webhook (a soft delete): it stops receiving events and no longer appears in webhook lists, and its URL can be registered again in the consumer. Its subscriptions and delivery history are kept, and its deliveries stay listed until retention removes them. Queued deliveries for it fail with "Webhook was deleted". This cannot be undone. A second delete returns 404. Portal tokens: allowed, as DELETE /portal/api/webhooks/{webhook_id}. operationId: deleteWebhook parameters: - description: Tenant consumer the webhook belongs to. in: path name: consumer required: true schema: description: Tenant consumer the webhook belongs to. type: string - description: Webhook id (UUID). in: path name: webhook_id required: true schema: description: Webhook id (UUID). type: string responses: "204": description: No Content "404": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Not Found "422": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Unprocessable Entity "500": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Internal Server Error summary: Delete a webhook tags: - Webhooks get: description: |- Returns one webhook's configuration, subscribed events, masked secrets, and current health label. 404 when it does not exist in this consumer or was deleted. Portal tokens: allowed, as GET /portal/api/webhooks/{webhook_id}. operationId: getWebhook parameters: - description: Tenant consumer the webhook belongs to. in: path name: consumer required: true schema: description: Tenant consumer the webhook belongs to. type: string - description: Webhook id (UUID). in: path name: webhook_id required: true schema: description: Webhook id (UUID). type: string responses: "200": content: application/json: schema: $ref: "#/components/schemas/WebhookOut" description: OK "404": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Not Found "422": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Unprocessable Entity "500": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Internal Server Error summary: Get a webhook by id tags: - Webhooks patch: description: |- Partially updates a webhook: only fields present in the body change. Within http_config only the sent fields change, and the merged result is validated as on register (400 if out of range). Sending events replaces every subscription of the webhook (see events). Setting active toggles delivery like :pause/:resume but never fails when the state is already as requested. Returns the updated webhook. Idempotent: repeating the same body leaves the same state. 409 when turning requires_transform on while a subscription has no enabled transform, or replacing events while it is on. The deprecated top-level timeout is applied only when http_config.request_timeout_seconds is not sent. Portal tokens: allowed, as PATCH /portal/api/webhooks/{webhook_id}. operationId: updateWebhook parameters: - description: Consumer the webhook belongs to. in: path name: consumer required: true schema: description: Consumer the webhook belongs to. type: string - description: Webhook id (UUID). in: path name: webhook_id required: true schema: description: Webhook id (UUID). type: string requestBody: content: application/json: schema: $ref: "#/components/schemas/PatchWebhookBody" required: true responses: "200": content: application/json: schema: $ref: "#/components/schemas/WebhookOut" description: OK "400": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Bad Request "404": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Not Found "409": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Conflict "422": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Unprocessable Entity "500": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Internal Server Error summary: Partially update a webhook tags: - Webhooks /v1/consumers/{consumer}/webhooks/{webhook_id}/health: get: description: |- Returns one webhook's health label plus attempt counts and per-category error counts (client, server, timeout, network, unexpected-status) over the last rules.window_hours hours, and the thresholds the label is derived from. The label lags by up to SPARROW_HEALTH_EVAL_INTERVAL. Counts are zero before the first attempt. 404 when the webhook is not in this consumer. Portal tokens: allowed, as GET /portal/api/webhooks/{webhook_id}/health. operationId: getWebhookHealth parameters: - description: Tenant consumer the webhook belongs to. in: path name: consumer required: true schema: description: Tenant consumer the webhook belongs to. type: string - description: Webhook id (UUID). in: path name: webhook_id required: true schema: description: Webhook id (UUID). type: string responses: "200": content: application/json: schema: $ref: "#/components/schemas/WebhookHealthOutputBody" description: OK "404": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Not Found "422": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Unprocessable Entity "500": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Internal Server Error summary: Get health status and metrics for a webhook tags: - Health /v1/consumers/{consumer}/webhooks/{webhook_id}:pause: post: description: |- Stops deliveries to this webhook without deleting it. Returns 204 with no body. Events keep fanning out to it: each new delivery, and any queued or retrying one, is held with status paused until it is retried after resuming. Holding never affects the webhook's health. 409 when the webhook is already paused. Portal tokens: allowed, as POST /portal/api/webhooks/{webhook_id}:pause. operationId: pauseWebhook parameters: - description: Tenant consumer the webhook belongs to. in: path name: consumer required: true schema: description: Tenant consumer the webhook belongs to. type: string - description: Webhook id (UUID). in: path name: webhook_id required: true schema: description: Webhook id (UUID). type: string responses: "204": description: No Content "404": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Not Found "409": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Conflict "422": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Unprocessable Entity "500": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Internal Server Error summary: Pause a webhook tags: - Webhooks /v1/consumers/{consumer}/webhooks/{webhook_id}:resume: post: description: |- Re-enables deliveries to a webhook paused by an operator or auto-disabled, and clears auto_disabled_at/auto_disabled_reason. Deliveries held while it was paused stay paused and are never sent automatically: the response says how many there are, so you can retry them with status=paused&webhook_id=... and the delivery retry endpoints. 409 when the webhook is already active. Portal tokens: allowed, as POST /portal/api/webhooks/{webhook_id}:resume. operationId: resumeWebhook parameters: - description: Tenant consumer the webhook belongs to. in: path name: consumer required: true schema: description: Tenant consumer the webhook belongs to. type: string - description: Webhook id (UUID). in: path name: webhook_id required: true schema: description: Webhook id (UUID). type: string responses: "200": content: application/json: schema: $ref: "#/components/schemas/ResumeWebhookOutputBody" description: OK "404": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Not Found "409": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Conflict "422": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Unprocessable Entity "500": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Internal Server Error summary: Resume a paused webhook tags: - Webhooks /v1/consumers/{consumer}/webhooks/{webhook_id}:rotateSecret: post: description: |- Replaces the webhook's HMAC signing secret with a newly generated one and returns it in plaintext. This response is the only time the new secret is shown; it is masked on every later read. There is no overlap window: every attempt made after the rotation, including retries of older deliveries, is signed with the new secret only, so update the receiver right away. Not idempotent: each call generates a new secret. The Ed25519 key, if any, is not rotated. Portal tokens: allowed, as POST /portal/api/webhooks/{webhook_id}:rotateSecret. operationId: rotateWebhookSecret parameters: - description: Tenant consumer the webhook belongs to. in: path name: consumer required: true schema: description: Tenant consumer the webhook belongs to. type: string - description: Webhook id (UUID). in: path name: webhook_id required: true schema: description: Webhook id (UUID). type: string responses: "200": content: application/json: schema: $ref: "#/components/schemas/RotateWebhookSecretOutputBody" description: OK "404": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Not Found "422": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Unprocessable Entity "500": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Internal Server Error summary: Rotate a webhook's signing secret tags: - Webhooks /v1/deliveries: get: description: |- Lists deliveries across every consumer, newest first, with the same filters, cursor pagination and prepare_retry side effect as the per-consumer route. Pass consumer to scope to one consumer. Portal tokens: not allowed. operationId: listDeliveriesGlobal parameters: - description: Filter to one consumer; omit to list deliveries across all consumers. explode: false in: query name: consumer schema: description: Filter to one consumer; omit to list deliveries across all consumers. type: string - description: Filter to deliveries for one webhook (UUID; 400 otherwise). explode: false in: query name: webhook_id schema: description: Filter to deliveries for one webhook (UUID; 400 otherwise). type: string - description: Filter to deliveries for one pushed event occurrence (UUID; 400 otherwise). explode: false in: query name: event_id schema: description: Filter to deliveries for one pushed event occurrence (UUID; 400 otherwise). type: string - description: Filter by delivery status. explode: false in: query name: status schema: description: Filter by delivery status. enum: - pending - sending - success - failed - retrying - expired - paused type: string - description: Filter by the most recent attempt's error_category, e.g. server_error, client_error, timeout. explode: false in: query name: error_category schema: description: Filter by the most recent attempt's error_category, e.g. server_error, client_error, timeout. type: string - description: Filter to deliveries created by one subscription (UUID), e.g. its paused deliveries. explode: false in: query name: subscription_id schema: description: Filter to deliveries created by one subscription (UUID), e.g. its paused deliveries. type: string - description: Filter to deliveries of one event type, e.g. order.created. Also matches deliveries whose subscription has since been replaced or deleted. explode: false in: query name: event_name schema: description: Filter to deliveries of one event type, e.g. order.created. Also matches deliveries whose subscription has since been replaced or deleted. type: string - description: "Only deliveries created at or after this time: YYYY-MM-DD (start of that day, UTC) or an RFC3339 timestamp (e.g. an import's imported_at)." explode: false in: query name: created_after schema: description: "Only deliveries created at or after this time: YYYY-MM-DD (start of that day, UTC) or an RFC3339 timestamp (e.g. an import's imported_at)." type: string - description: "Only deliveries created at or before this time: YYYY-MM-DD (through the end of that day, UTC) or an RFC3339 timestamp." explode: false in: query name: created_before schema: description: "Only deliveries created at or before this time: YYYY-MM-DD (through the end of that day, UTC) or an RFC3339 timestamp." type: string - description: "Side effect: when true, also snapshots the ids of every delivery matching the filters (not just this page; at most 10,000, more is an error) into a batch job and returns its id as retry_id. Pass it to POST .../deliveries:retryBatch within 15 minutes. Each call creates a new snapshot." explode: false in: query name: prepare_retry schema: default: false description: "Side effect: when true, also snapshots the ids of every delivery matching the filters (not just this page; at most 10,000, more is an error) into a batch job and returns its id as retry_id. Pass it to POST .../deliveries:retryBatch within 15 minutes. Each call creates a new snapshot." type: boolean - description: Maximum items to return, 1 to 1000. explode: false in: query name: limit schema: default: 50 description: Maximum items to return, 1 to 1000. format: int32 maximum: 1000 minimum: 1 type: integer - description: "Continue after the page this came from: pagination.next_cursor of the previous response. Stable while new deliveries arrive." explode: false in: query name: cursor schema: description: "Continue after the page this came from: pagination.next_cursor of the previous response. Stable while new deliveries arrive." type: string - description: "Removed: page with cursor. Any value other than 0 is rejected with 400, so a client still paging by offset fails loudly instead of re-reading the first page." explode: false in: query name: offset schema: default: 0 description: "Removed: page with cursor. Any value other than 0 is rejected with 400, so a client still paging by offset fails loudly instead of re-reading the first page." format: int32 type: integer responses: "200": content: application/json: schema: $ref: "#/components/schemas/ListDeliveriesOutputBody" description: OK default: content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Error summary: List deliveries across all consumers tags: - Deliveries /v1/deliveries/{delivery_id}: get: description: |- Looks a delivery up by id in any consumer, for callers that only have the id (e.g. the X-Sparrow-Delivery-ID header a receiver logged). 404 when it does not exist. Portal tokens: not allowed. operationId: getDeliveryGlobal parameters: - description: Delivery id (UUID). in: path name: delivery_id required: true schema: description: Delivery id (UUID). type: string responses: "200": content: application/json: schema: $ref: "#/components/schemas/DeliveryItem" description: OK "404": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Not Found "422": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Unprocessable Entity "500": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Internal Server Error summary: Get a delivery by id (any consumer) tags: - Deliveries /v1/deliveries/{delivery_id}/attempts: get: description: |- Same as getDeliveryAttempts, without a consumer: the delivery is looked up by id alone, and an unknown id returns an empty list. Portal tokens: not allowed. operationId: getDeliveryAttemptsGlobal parameters: - description: Delivery id (UUID). in: path name: delivery_id required: true schema: description: Delivery id (UUID). type: string responses: "200": content: application/json: schema: $ref: "#/components/schemas/AttemptsOutputBody" description: OK "404": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Not Found "422": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Unprocessable Entity "500": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Internal Server Error summary: Get a delivery's per-attempt history (any consumer) tags: - Deliveries /v1/deliveries/{delivery_id}:retry: post: description: |- Same as retryDelivery, for a delivery in any consumer. Portal tokens: not allowed. operationId: retryDeliveryGlobal parameters: - description: Delivery id (UUID). in: path name: delivery_id required: true schema: description: Delivery id (UUID). type: string responses: "200": content: application/json: schema: $ref: "#/components/schemas/RetryOutputBody" description: OK "400": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Bad Request "404": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Not Found "409": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Conflict "422": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Unprocessable Entity "500": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Internal Server Error summary: Retry a single delivery (any consumer) tags: - Deliveries /v1/event-types: get: description: |- Lists registered event types sorted by name, optionally only active ones. Sparrow's own sparrow.* events are listed only with consumer=_sparrow. Offset paginated with total_count. Portal tokens: allowed, as GET /portal/api/event-types; consumer is set to the token's consumer. operationId: listEventTypes parameters: - description: true returns only active event types; false returns all. explode: false in: query name: active_only schema: default: false description: true returns only active event types; false returns all. type: boolean - description: "Return the event types this consumer can subscribe to: Sparrow's own sparrow.* events for the _sparrow consumer, tenant event types for any other consumer or when omitted." explode: false in: query name: consumer schema: description: "Return the event types this consumer can subscribe to: Sparrow's own sparrow.* events for the _sparrow consumer, tenant event types for any other consumer or when omitted." type: string - description: Maximum items to return, 1 to 1000. explode: false in: query name: limit schema: default: 50 description: Maximum items to return, 1 to 1000. format: int32 maximum: 1000 minimum: 1 type: integer - description: Number of items to skip, for pagination. explode: false in: query name: offset schema: default: 0 description: Number of items to skip, for pagination. format: int32 type: integer responses: "200": content: application/json: schema: $ref: "#/components/schemas/ListEventTypesOutputBody" description: OK default: content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Error summary: List event type definitions tags: - Event Types post: description: |- Defines a named event type at version 1, optionally with a JSON Schema; the response includes a sample_payload generated from the schema. Pushing an event requires its type to be registered (unless SPARROW_AUTO_REGISTER_EVENTS=true). Event types are never deleted; deactivate one with active: false. Not idempotent: 409 when the name already exists (use PATCH, or :import, to change it). 400 for a missing, too long or reserved (sparrow.*) name or an invalid schema. Portal tokens: not allowed. operationId: registerEventType requestBody: content: application/json: schema: $ref: "#/components/schemas/EventTypeBody" required: true responses: "201": content: application/json: schema: $ref: "#/components/schemas/EventTypeItem" description: Created "400": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Bad Request "409": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Conflict "422": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Unprocessable Entity "500": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Internal Server Error summary: Register an event type definition tags: - Event Types /v1/event-types/{name}: get: description: |- Returns one event type's current version: schema, sample payload, and metadata. 404 when the name is not registered. Portal tokens: allowed, as GET /portal/api/event-types/{name}. operationId: getEventType parameters: - description: Event type name, e.g. order.created. in: path name: name required: true schema: description: Event type name, e.g. order.created. type: string responses: "200": content: application/json: schema: $ref: "#/components/schemas/EventTypeItem" description: OK "404": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Not Found "422": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Unprocessable Entity "500": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Internal Server Error summary: Get an event type by name tags: - Event Types patch: description: |- Partially updates an event type: only fields present in the request body are changed (metadata, when sent, replaces the whole map). A schema change creates a new version and keeps the previous one; adding a first schema to a schema-less version, and any change to description, metadata or active, updates the current version in place. The response's change field says which happened; repeating the same body is a no-op (change.action unchanged). A breaking schema change (see change.compatibility) to an event type that subscriptions receive is refused with 409 unless allow_breaking=true. 404 when the name is not registered. Portal tokens: not allowed. operationId: updateEventType parameters: - description: Event type name, e.g. order.created. in: path name: name required: true schema: description: Event type name, e.g. order.created. type: string - description: Apply a breaking schema change even though subscriptions receive this event type. Without it, a breaking change to a subscribed type is refused with 409 and the reasons. explode: false in: query name: allow_breaking schema: default: false description: Apply a breaking schema change even though subscriptions receive this event type. Without it, a breaking change to a subscribed type is refused with 409 and the reasons. type: boolean requestBody: content: application/json: schema: $ref: "#/components/schemas/EventTypeBody" required: true responses: "200": content: application/json: schema: $ref: "#/components/schemas/UpdateEventTypeOutputBody" description: OK "400": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Bad Request "404": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Not Found "409": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Conflict "422": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Unprocessable Entity "500": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Internal Server Error summary: Update an event type definition tags: - Event Types /v1/event-types/{name}/versions: get: description: |- Returns the version history of an event type, newest first. Not paginated. Event types are never deleted; to retire one, set active to false. Portal tokens: allowed, as GET /portal/api/event-types/{name}/versions. operationId: listEventTypeVersions parameters: - description: Event type name, e.g. order.created. in: path name: name required: true schema: description: Event type name, e.g. order.created. type: string responses: "200": content: application/json: schema: $ref: "#/components/schemas/ListEventTypeVersionsOutputBody" description: OK "404": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Not Found "422": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Unprocessable Entity "500": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Internal Server Error summary: List every version of an event type tags: - Event Types /v1/event-types/{name}/versions/{version}: get: description: |- Returns one version's schema, sample payload and description. 404 when the name or version does not exist. Portal tokens: allowed, as GET /portal/api/event-types/{name}/versions/{version}. operationId: getEventTypeVersion parameters: - description: Event type name, e.g. order.created. in: path name: name required: true schema: description: Event type name, e.g. order.created. type: string - description: Version number. in: path name: version required: true schema: description: Version number. format: int64 minimum: 1 type: integer responses: "200": content: application/json: schema: $ref: "#/components/schemas/EventTypeVersionItem" description: OK "404": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Not Found "422": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Unprocessable Entity "500": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Internal Server Error summary: Get one version of an event type tags: - Event Types /v1/event-types/{name}:validate: post: description: |- Checks a payload against the event type's current JSON Schema without pushing an event or storing anything. pushEvent accepts events regardless of schema validity; this lets callers (e.g. the Push Test Event UI) enforce a hard gate before submitting. An invalid payload is still 200, with valid: false. 404 when the name is not registered. Portal tokens: not allowed. operationId: validateEventPayload parameters: - description: Event type name, e.g. order.created. in: path name: name required: true schema: description: Event type name, e.g. order.created. type: string requestBody: content: application/json: schema: $ref: "#/components/schemas/ValidateEventPayloadInputBody" required: true responses: "200": content: application/json: schema: $ref: "#/components/schemas/ValidateEventPayloadOutputBody" description: OK "404": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Not Found "422": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Unprocessable Entity "500": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Internal Server Error summary: Validate a payload against an event type's JSON Schema tags: - Event Types /v1/event-types:export: post: description: |- Returns the current definitions of the selected event types as one JSON bundle, ready to import into another environment with importEventTypes. Choose exactly one of names, prefix or all. An unknown name fails the whole export. Sparrow's own sparrow.* event types are never exported. Read-only despite POST (the selection travels in the body). Portal tokens: not allowed. operationId: exportEventTypes requestBody: content: application/json: schema: $ref: "#/components/schemas/ExportEventTypesInputBody" required: true responses: "200": content: application/json: schema: $ref: "#/components/schemas/EventTypeBundle" description: OK "400": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Bad Request "404": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Not Found "422": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Unprocessable Entity "500": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Internal Server Error summary: Export event type definitions as a bundle tags: - Event Types /v1/event-types:import: post: description: |- Applies a bundle in one transaction: each item replaces the named event type using the usual version rules, or nothing is written. Event types not in the bundle are never touched; an import does not delete. The full per-item result is returned either way. Nothing is written for a dry run, when a stamp warning is not acknowledged, or when a change is breaking for subscriptions and allow_breaking is not set; blocked_by says why. The body may be an exported bundle as-is, plus the options. Idempotent: importing the same bundle again reports every item unchanged. The body is limited to 5 MiB and 500 items. Portal tokens: not allowed. operationId: importEventTypes requestBody: content: application/json: schema: $ref: "#/components/schemas/ImportEventTypesInputBody" required: true responses: "200": content: application/json: schema: $ref: "#/components/schemas/ImportEventTypesOutputBody" description: OK "400": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Bad Request "422": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Unprocessable Entity "500": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Internal Server Error summary: Import a bundle of event type definitions tags: - Event Types /v1/events: get: description: |- Lists event occurrences across every consumer, newest first, with the same filters, cursor pagination and prepare_repush side effect as the per-consumer route. Pass consumer to scope to one consumer. Portal tokens: not allowed. operationId: listEventOccurrencesGlobal parameters: - description: Filter to one consumer; omit to list occurrences across all consumers. explode: false in: query name: consumer schema: description: Filter to one consumer; omit to list occurrences across all consumers. type: string - description: Filter to occurrences of this event type name. explode: false in: query name: event schema: description: Filter to occurrences of this event type name. type: string - description: "Filter by schema validation result: valid or invalid. Other values are 400." explode: false in: query name: schema_valid schema: description: "Filter by schema validation result: valid or invalid. Other values are 400." type: string - description: Filter by labels as comma-separated key=value pairs, e.g. region=eu,tier=gold. Matches occurrences whose labels contain every pair. explode: false in: query name: labels schema: description: Filter by labels as comma-separated key=value pairs, e.g. region=eu,tier=gold. Matches occurrences whose labels contain every pair. type: string - description: "Only occurrences created at or after this time: YYYY-MM-DD (start of that day, UTC) or an RFC3339 timestamp." explode: false in: query name: created_after schema: description: "Only occurrences created at or after this time: YYYY-MM-DD (start of that day, UTC) or an RFC3339 timestamp." type: string - description: "Only occurrences created at or before this time: YYYY-MM-DD (through the end of that day, UTC) or an RFC3339 timestamp." explode: false in: query name: created_before schema: description: "Only occurrences created at or before this time: YYYY-MM-DD (through the end of that day, UTC) or an RFC3339 timestamp." type: string - description: "Side effect: when true, also snapshots the ids of every occurrence matching the filters (not just this page; at most 10,000, more is an error) into a batch job and returns its id as repush_id. Pass it to POST .../events:rePush within 15 minutes. Each call creates a new snapshot." explode: false in: query name: prepare_repush schema: default: false description: "Side effect: when true, also snapshots the ids of every occurrence matching the filters (not just this page; at most 10,000, more is an error) into a batch job and returns its id as repush_id. Pass it to POST .../events:rePush within 15 minutes. Each call creates a new snapshot." type: boolean - description: Maximum items to return, 1 to 1000. explode: false in: query name: limit schema: default: 50 description: Maximum items to return, 1 to 1000. format: int32 maximum: 1000 minimum: 1 type: integer - description: "Continue after the page this came from: pagination.next_cursor of the previous response. Stable while new events arrive." explode: false in: query name: cursor schema: description: "Continue after the page this came from: pagination.next_cursor of the previous response. Stable while new events arrive." type: string - description: "Removed: page with cursor. Any value other than 0 is rejected with 400, so a client still paging by offset fails loudly instead of re-reading the first page." explode: false in: query name: offset schema: default: 0 description: "Removed: page with cursor. Any value other than 0 is rejected with 400, so a client still paging by offset fails loudly instead of re-reading the first page." format: int32 type: integer responses: "200": content: application/json: schema: $ref: "#/components/schemas/ListEventOccurrencesOutputBody" description: OK default: content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Error summary: List pushed event occurrences across all consumers tags: - Events /v1/events/{event_id}: get: description: |- Returns one event occurrence's payload, labels, and delivery outcome counts. Event ids are globally unique, so no consumer is required. 400 when event_id is not a UUID, 404 when it does not exist (or was removed by retention). Portal tokens: not allowed. operationId: getEventOccurrence parameters: - description: Event occurrence id (UUID). in: path name: event_id required: true schema: description: Event occurrence id (UUID). type: string responses: "200": content: application/json: schema: $ref: "#/components/schemas/EventOccurrenceItem" description: OK "400": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Bad Request "404": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Not Found "422": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Unprocessable Entity "500": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Internal Server Error summary: Get a pushed event occurrence by id tags: - Events /v1/events/{event_id}:repush: post: description: |- Pushes a stored event occurrence again: creates a new event occurrence (new event_id) with the same consumer, event type, payload, labels, metadata and TTL, validates the payload against the current schema, and fans it out to the subscriptions that match now. The original occurrence and its deliveries are unchanged. Not idempotent: each call creates another event. To resend one existing delivery instead, retry the delivery. 404 when the event does not exist; the same errors as pushEvent when the event type is now inactive or unregistered. Portal tokens: not allowed. operationId: repushEvent parameters: - description: Event occurrence id (UUID). in: path name: event_id required: true schema: description: Event occurrence id (UUID). type: string responses: "200": content: application/json: schema: $ref: "#/components/schemas/RepushEventOutputBody" description: OK "400": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Bad Request "404": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Not Found "409": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Conflict "422": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Unprocessable Entity "500": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Internal Server Error summary: Replay a single pushed event occurrence tags: - Events /v1/health-summary: get: description: |- Returns how many webhooks are currently healthy, degraded, unhealthy, or unknown, in one consumer or across every consumer — for a top-level dashboard tile, plus the thresholds behind those statuses. Deleted webhooks are not counted. Portal tokens: not allowed. operationId: getHealthSummary parameters: - description: Count one consumer's webhooks; omit to count across all consumers. explode: false in: query name: consumer schema: description: Count one consumer's webhooks; omit to count across all consumers. type: string responses: "200": content: application/json: schema: $ref: "#/components/schemas/HealthSummaryOutputBody" description: OK default: content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Error summary: Get aggregate webhook health counts tags: - Health /v1/invites: get: description: |- Lists invites, newest first. Only pending invites unless include_inactive is set. Secrets are never returned. Not paginated. Portal tokens: not allowed. operationId: listInvites parameters: - description: Also return redeemed, cancelled, and expired invites. explode: false in: query name: include_inactive schema: description: Also return redeemed, cancelled, and expired invites. type: boolean responses: "200": content: application/json: schema: $ref: "#/components/schemas/ListInvitesOutputBody" description: OK "422": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Unprocessable Entity "500": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Internal Server Error "503": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Service Unavailable summary: List invites tags: - Access post: description: |- Creates a single-use, expiring invite. Opening the returned path in the UI exchanges it for a new access token named after the invitee (POST /invite/redeem), so nobody pastes a key into chat. A consumer invite opens that consumer's portal instead of the operator console. The secret is returned only here. Redeeming happens at POST /invite/redeem (outside /v1, no credential needed); an unknown, used, cancelled or expired invite gets 400 invalid_invite there. Portal tokens: not allowed. operationId: createInvite requestBody: content: application/json: schema: $ref: "#/components/schemas/CreateInviteBody" required: true responses: "201": content: application/json: schema: $ref: "#/components/schemas/CreateInviteOutputBody" description: Created "400": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Bad Request "422": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Unprocessable Entity "500": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Internal Server Error "503": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Service Unavailable summary: Invite someone with a one-time link tags: - Access /v1/invites/{invite_id}: delete: description: |- Cancels a pending invite so it can no longer be redeemed. Returns 204. 409 when the invite is no longer pending (redeemed, cancelled or expired); a token already created from it keeps working, so revoke that separately. 404 for an unknown id. Portal tokens: not allowed. operationId: cancelInvite parameters: - description: Invite id (the id field, not the secret). in: path name: invite_id required: true schema: description: Invite id (the id field, not the secret). type: string responses: "204": description: No Content "404": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Not Found "409": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Conflict "422": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Unprocessable Entity "500": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Internal Server Error "503": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Service Unavailable summary: Cancel a pending invite tags: - Access /v1/recipes: get: description: |- Lists the adapter recipes built into this Sparrow server. A recipe pre-fills a webhook destination, headers and a subscription transform; clients (the UI, sparrow use) substitute the params and register the webhook. Static and read-only. Portal tokens: not allowed. operationId: listRecipes responses: "200": content: application/json: schema: $ref: "#/components/schemas/ListRecipesOutputBody" description: OK "500": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Internal Server Error summary: List shipped adapter recipes tags: - Recipes /v1/stats: get: description: |- Returns the same counters as the per-consumer stats endpoint, summed across every consumer, with the same lag. Portal tokens: not allowed. operationId: getGlobalStats responses: "200": content: application/json: schema: $ref: "#/components/schemas/ConsumerStatsOutputBody" description: OK default: content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Error summary: Get aggregate delivery statistics across all consumers tags: - Webhooks /v1/subscriptions: get: description: |- Lists subscriptions across every consumer with the same filters and pagination as the per-consumer route. Pass consumer to scope to one consumer. Portal tokens: not allowed. operationId: listSubscriptionsGlobal parameters: - description: Filter to one consumer; omit to list subscriptions across all consumers. explode: false in: query name: consumer schema: description: Filter to one consumer; omit to list subscriptions across all consumers. type: string - description: "Filter to subscriptions for one webhook (UUID). Takes precedence over event_name: when both are given, event_name is ignored." explode: false in: query name: webhook_id schema: description: "Filter to subscriptions for one webhook (UUID). Takes precedence over event_name: when both are given, event_name is ignored." type: string - description: Filter to subscriptions with exactly this event_name ("*" matches only catch-all subscriptions). explode: false in: query name: event_name schema: description: Filter to subscriptions with exactly this event_name ("*" matches only catch-all subscriptions). type: string - description: Maximum items to return, 1 to 1000. explode: false in: query name: limit schema: default: 50 description: Maximum items to return, 1 to 1000. format: int32 maximum: 1000 minimum: 1 type: integer - description: Number of items to skip, for pagination. explode: false in: query name: offset schema: default: 0 description: Number of items to skip, for pagination. format: int32 type: integer responses: "200": content: application/json: schema: $ref: "#/components/schemas/ListSubscriptionsOutputBody" description: OK default: content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Error summary: List subscriptions across all consumers tags: - Subscriptions /v1/subscriptions:draftTemplate: post: description: |- Drafts a transform_template from a plain-language description, grounded in the event type's JSON Schema and sample payload, the template helper catalog, and optionally a shipped recipe's destination format or an example body the receiver expects. Every draft is rendered against the sample payload (as POST /v1/subscriptions:testTemplate does) and repaired until it renders, so the returned template is known to work. Nothing is saved: put the template into a subscription's transform_template. Requires SPARROW_AI_ENABLED=true and the provider settings (SPARROW_AI_API_KEY for Anthropic, or SPARROW_AI_PROVIDER=openai with SPARROW_AI_BASE_URL and SPARROW_AI_MODEL for any OpenAI-compatible server); otherwise 503, naming the missing setting. Only the sample payload (registered or provided in the request), the schema, and the request's own text are sent to the model, never stored events, headers, or secrets. Not idempotent (model output varies). 404 for an unknown event type or recipe, 429 when the provider rate-limits, 504 when the draft takes longer than SPARROW_AI_TIMEOUT (default 3m). Portal tokens: not allowed. operationId: draftSubscriptionTemplate requestBody: content: application/json: schema: $ref: "#/components/schemas/DraftTemplateBody" required: true responses: "200": content: application/json: schema: $ref: "#/components/schemas/DraftTemplateOutputBody" description: OK "400": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Bad Request "404": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Not Found "422": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Unprocessable Entity "429": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Too Many Requests "500": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Internal Server Error "503": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Service Unavailable "504": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Gateway Timeout summary: Draft a transform template with AI tags: - Subscriptions /v1/subscriptions:draftTemplatePrompt: post: description: |- Returns the same prompt POST /v1/subscriptions:draftTemplate would send, as one self-contained text to paste into any chat assistant (ChatGPT, Claude, a local model). Works whether or not the server has an AI provider configured, so installs without SPARROW_AI_* still get a one-click prompt. The user pastes the assistant's template back into the subscription editor and checks it with Run Preview. Nothing is sent anywhere by Sparrow; a docs_url, if given, is fetched from a public address only to include an excerpt. No side effects. Portal tokens: not allowed. operationId: buildSubscriptionTemplatePrompt requestBody: content: application/json: schema: $ref: "#/components/schemas/DraftTemplateBody" required: true responses: "200": content: application/json: schema: $ref: "#/components/schemas/DraftTemplatePromptOutputBody" description: OK "400": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Bad Request "404": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Not Found "422": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Unprocessable Entity "500": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Internal Server Error summary: Build the AI drafting prompt without calling a model tags: - Subscriptions /v1/subscriptions:testTemplate: post: description: |- Dry-runs a transform template against payload when given, else the named event type's generated sample payload, without creating a subscription or any delivery. Use this to iterate on transform_template before saving it; pass a real event's payload to see what a receiver would get. Stateless and idempotent. 400 when the template does not parse or fails to render (with template_missing_key error, a key the payload lacks is an error), 404 when the event type is not registered, 503 when the template engine is at capacity (retry). Portal tokens: allowed, as POST /portal/api/subscriptions:testTemplate. operationId: testSubscriptionTemplate requestBody: content: application/json: schema: $ref: "#/components/schemas/TestTemplateBody" required: true responses: "200": content: application/json: schema: $ref: "#/components/schemas/TestTemplateOutputBody" description: OK "400": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Bad Request "404": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Not Found "422": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Unprocessable Entity "500": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Internal Server Error "503": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Service Unavailable summary: Render a transform template against a sample or given payload tags: - Subscriptions /v1/template-functions: get: description: |- Lists the helper functions available to subscription transform templates (on top of Go text/template built-ins), each with Markdown documentation. Static: the same for every caller. Portal tokens: allowed, as GET /portal/api/template-functions. operationId: getTemplateFunctions responses: "200": content: application/json: schema: $ref: "#/components/schemas/TemplateFunctionsOutputBody" description: OK default: content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Error summary: List available payload-transformation template functions tags: - Webhooks /v1/tls/crls: get: description: Shows the CRLs the instance that answers has cached for checking intermediates in receivers' TLS chains, with when each was fetched and any error. CRLs are downloaded the first time a chain needs them, refreshed in the background by the next handshake once older than SPARROW_CRL_MAX_AGE, and on demand with refreshCrls. Each instance keeps its own cache, so behind a load balancer this shows one instance. operationId: listCrls responses: "200": content: application/json: schema: $ref: "#/components/schemas/ListCrlsOutputBody" description: OK default: content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Error summary: List cached CRLs tags: - Server /v1/tls/crls:refresh: post: description: "Asks every instance (through Postgres NOTIFY) to download its cached CRLs now, for when a CA revokes an intermediate and deliveries should stop trusting it before SPARROW_CRL_MAX_AGE passes. Returns 202 once the request is sent; check listCrls for the result. One request a minute on each server: more answer 429. 409 when SPARROW_TLS_REVOCATION=off." operationId: refreshCrls responses: "202": description: Accepted "409": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Conflict "429": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Too Many Requests "500": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Internal Server Error summary: Refresh cached CRLs on every instance tags: - Server /v1/tokens: get: description: |- Lists access tokens, newest first. Secrets are never returned. Revoked and expired tokens are hidden unless include_inactive is set. Not paginated. Portal tokens: not allowed. operationId: listTokens parameters: - description: Only tokens limited to this consumer. Omit to list every token, tenant-wide ones included. explode: false in: query name: consumer schema: description: Only tokens limited to this consumer. Omit to list every token, tenant-wide ones included. type: string - description: Also return revoked and expired tokens. explode: false in: query name: include_inactive schema: description: Also return revoked and expired tokens. type: boolean responses: "200": content: application/json: schema: $ref: "#/components/schemas/ListTokensOutputBody" description: OK "422": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Unprocessable Entity "500": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Internal Server Error "503": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Service Unavailable summary: List access tokens tags: - Access post: description: |- Creates a named token. A tenant-wide token (no consumer) works exactly like SPARROW_API_KEY but can be revoked on its own and never needs the master key to be shared. A consumer token only works through the portal API (/portal/api/), limited to that consumer, and comes with a ready-made portal link (portal_path) to embed or hand to the end consumer. The secret is returned once, or again for the same external_id while the token is valid. Without external_id every call creates a new token. 400 for an invalid consumer, both ttl_seconds and never_expires, never_expires or more than 30 days on a consumer token, or external_id without a consumer. 503 when the token store is unavailable, or for external_id when no keyring is configured. Portal tokens: not allowed. operationId: createToken requestBody: content: application/json: schema: $ref: "#/components/schemas/CreateTokenBody" required: true responses: "201": content: application/json: schema: $ref: "#/components/schemas/CreateTokenOutputBody" description: Created "400": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Bad Request "422": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Unprocessable Entity "500": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Internal Server Error "503": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Service Unavailable summary: Create an access token tags: - Access /v1/tokens/{token_id}: delete: description: |- Revokes a token: it stops working immediately on this server and within 30 seconds on other instances. Revoking is permanent and idempotent (204 again for an already revoked token). Returns 204; 404 for an unknown id. Portal tokens: not allowed. operationId: revokeToken parameters: - description: Token id (the id field, not the secret). in: path name: token_id required: true schema: description: Token id (the id field, not the secret). type: string responses: "204": description: No Content "404": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Not Found "422": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Unprocessable Entity "500": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Internal Server Error "503": content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Service Unavailable summary: Revoke an access token tags: - Access /v1/webhooks: get: description: |- Lists webhooks across every consumer, newest first, with masked secrets. Pass consumer to scope to one consumer, health to filter by label, or webhook_id for an id-only lookup when the consumer is not known. Offset paginated with total_count. Portal tokens: not allowed. operationId: listWebhooksByHealth parameters: - description: Filter to one consumer; omit to list webhooks across all consumers. explode: false in: query name: consumer schema: description: Filter to one consumer; omit to list webhooks across all consumers. type: string - description: Filter to webhooks with this computed health status. explode: false in: query name: health schema: description: Filter to webhooks with this computed health status. enum: - healthy - degraded - unhealthy - unknown type: string - description: Look up a single webhook by id when its consumer is unknown. explode: false in: query name: webhook_id schema: description: Look up a single webhook by id when its consumer is unknown. type: string - description: Maximum items to return; values above 1000 are capped at 1000. explode: false in: query name: limit schema: default: 50 description: Maximum items to return; values above 1000 are capped at 1000. format: int32 type: integer - description: Number of items to skip, for pagination. explode: false in: query name: offset schema: default: 0 description: Number of items to skip, for pagination. format: int32 type: integer responses: "200": content: application/json: schema: $ref: "#/components/schemas/ListWebhooksOutputBody" description: OK default: content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Error summary: List webhooks across all consumers tags: - Health /v1/whoami: get: description: |- Reports whether auth is on and which credential (master key or access token) authenticated this request. No side effects. Useful to check a key or token before using it. Portal tokens: not allowed. operationId: getWhoAmI responses: "200": content: application/json: schema: $ref: "#/components/schemas/WhoAmIOutputBody" description: OK default: content: application/problem+json: schema: $ref: "#/components/schemas/ErrorModel" description: Error summary: Show which credential this request used tags: - Access security: - ApiKeyAuth: [] - {} tags: - description: Registered HTTP endpoints and their delivery configuration (retries, timeouts, signing, rate limits). Secrets are always masked in responses except once, at creation time. name: Webhooks - description: Named, versioned schemas for the events your system can produce. Schema validation is soft — invalid payloads are stored with warnings, not rejected. name: Event Types - description: Pushed occurrences of an event type. Pushing an event asynchronously fans it out to every subscription that matches its event name and label filters. name: Events - description: The link between one webhook and one event type within a consumer. Carries the optional payload-transform template and label filters that decide whether a given event occurrence produces a delivery. name: Subscriptions - description: Individual delivery attempts and their per-attempt retry history. Failed deliveries can be retried singly or in bulk via a prepared batch snapshot. name: Deliveries - description: Rolling per-webhook health status computed from recent delivery outcomes, plus cross-consumer aggregate counts for dashboards. name: Health - description: Opt-in email recipients for Sparrow's own self-generated webhook.health_changed, webhook.delivery_failed and webhook.disabled system events. name: Alert Configs - description: Shipped webhook adapter recipes that pre-fill destinations, headers, and payload transforms. name: Recipes - description: Access tokens and invites. Tenant-wide tokens work like SPARROW_API_KEY but can be revoked one by one; consumer-scoped tokens only reach the portal API. Secrets are shown once. name: Access - description: Deployment-level information about this server, such as which optional features are configured. name: Server