{
  "openapi": "3.1.0",
  "info": {
    "title": "SigWise API",
    "version": "1.0.0",
    "summary": "Send events about your objects, get typed answers back.",
    "description": "SigWise reads the events and messages on your platform and answers the\nquestions you configure about each object (a user, a listing, an order)\nas typed **signals**: probabilities (`noul`), positions on a labelled\nspectrum (`score`) and one-of-N classifications (`choice`).\n\nYou configure signals, send events about objects, and read the latest\nanswers, or get them pushed to you through webhooks and rules.\n\n## Authentication\n\nIntegrations authenticate with an API key, which is a pair:\n\n- a public **key ID**, sent as the `api_key` query parameter\n  (or as the JWT header `kid`);\n- a **signing secret**, which never leaves your server.\n\nEvery request carries `Authorization: Bearer <jwt>`: an HS256 JWT you sign\nwith the whole secret string. It needs `iat` and `exp` claims at most five\nminutes apart (with ±60 s of clock skew), and can bind itself to one\nrequest with `htm` (the HTTP method) and `htu` (the path). The official\nSDKs do this for you on every request.\n\n## Errors\n\nEvery error uses the same envelope,\n`{\"error\": {\"code\": \"not_found\", \"message\": \"signal not found\"}}`, with a\nstable machine-readable `code` and a human-readable `message`.\n\n## Rate limits\n\nRequests under `/v1` are limited per account (every API key and console\nsession of an account share one budget; 3,000 requests a minute by\ndefault). Over the limit, the API answers `429` with code `rate_limited`\nand a `Retry-After` header in seconds. Batch several events into one\ningest request rather than sending them one by one.\n",
    "contact": {
      "name": "SigWise",
      "url": "https://sigwise.ai"
    },
    "license": {
      "name": "Proprietary",
      "identifier": "LicenseRef-Proprietary"
    }
  },
  "servers": [
    {
      "url": "https://api.sigwise.ai",
      "description": "Production"
    },
    {
      "url": "http://localhost:8080",
      "description": "Local development"
    }
  ],
  "security": [
    {
      "ApiKeyId": [],
      "SignedJWT": []
    },
    {
      "ConsoleToken": []
    }
  ],
  "tags": [
    {
      "name": "Objects",
      "description": "An object is anything you want answers about: a user, a listing, an\norder. It is identified by your own `object_id` and exists as soon as\nyou send its first event.\n"
    },
    {
      "name": "Events",
      "description": "Events and messages are the evidence an object's answers are computed\nfrom. Ingestion is asynchronous by default and returns `202`; set\n`wait: true` to score the object inline instead (direct moderation).\n"
    },
    {
      "name": "Signals",
      "description": "A signal is a question you ask about every object, such as \"is this a\nscammer?\" (`noul`), \"how trustworthy is this user?\" (`score`) or \"what is\ntheir buyer intent?\" (`choice`).\n"
    },
    {
      "name": "Webhooks",
      "description": "Webhook endpoints receive a signed `POST` for the event types they\nsubscribe to: `analysis.completed` each time an analysis completes, and\n`rule.triggered` when a rule with a webhook action fires. Deliveries\nretry with backoff and land in a dead-letter queue you can replay.\n"
    },
    {
      "name": "Rules",
      "description": "A rule fires an action (email, Slack or webhook) when an object's signal\nvalues cross a line you care about, such as `is_scammer >= 90`.\n"
    },
    {
      "name": "API keys",
      "description": "Create, rotate and revoke the keys your integrations sign requests with."
    },
    {
      "name": "Billing",
      "description": "Prepaid balance, the billing ledger, and card top-ups."
    },
    {
      "name": "Usage",
      "description": "Analysis volume, spend and token usage over a date range."
    },
    {
      "name": "Account",
      "description": "The authenticated principal and tenant settings."
    },
    {
      "name": "Console",
      "description": "Endpoints used by the SigWise console for human sign-in and account\nmanagement. They authenticate with a console token, not an API key, and\nare not part of the SDKs.\n"
    },
    {
      "name": "System",
      "description": "Health checks and this specification."
    }
  ],
  "x-tagGroups": [
    {
      "name": "Core",
      "tags": [
        "Objects",
        "Events",
        "Signals"
      ]
    },
    {
      "name": "Automation",
      "tags": [
        "Webhooks",
        "Rules"
      ]
    },
    {
      "name": "Account",
      "tags": [
        "Account",
        "API keys",
        "Billing",
        "Usage"
      ]
    },
    {
      "name": "Internal",
      "tags": [
        "Console",
        "System"
      ]
    }
  ],
  "paths": {
    "/healthz": {
      "get": {
        "tags": [
          "System"
        ],
        "operationId": "healthz",
        "summary": "Liveness",
        "description": "Returns `200` as long as the process is serving. It does not check dependencies.",
        "security": [],
        "x-sdk": false,
        "responses": {
          "200": {
            "description": "The process is up.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Health"
                },
                "example": {
                  "status": "ok"
                }
              }
            }
          }
        }
      }
    },
    "/metrics": {
      "get": {
        "tags": [
          "System"
        ],
        "operationId": "metrics",
        "summary": "Prometheus metrics",
        "description": "Operational metrics in the Prometheus text format: request rates and\nlatency by route, analysis outcomes, analyzer latency and throttling,\nqueue depth and lag, and running jobs per tenant. For the operator of\na deployment, not for integrations.\n\nRequires `Authorization: Bearer <METRICS_TOKEN>` when a token is\nconfigured, and is only served in production when one is. Returns\n`404` when metrics are disabled.\n",
        "security": [],
        "x-sdk": false,
        "responses": {
          "200": {
            "description": "The current metrics.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "The metrics token is missing or wrong."
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/readyz": {
      "get": {
        "tags": [
          "System"
        ],
        "operationId": "readyz",
        "summary": "Readiness",
        "description": "Returns `200` only when the database is reachable, so load balancers stop routing traffic during an outage.",
        "security": [],
        "x-sdk": false,
        "responses": {
          "200": {
            "description": "Ready to serve traffic.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Health"
                },
                "example": {
                  "status": "ready"
                }
              }
            }
          },
          "503": {
            "description": "The database is unreachable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_ready",
                    "message": "database unavailable"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/openapi.yaml": {
      "get": {
        "tags": [
          "System"
        ],
        "operationId": "getOpenAPIYAML",
        "summary": "OpenAPI specification (YAML)",
        "description": "This document, as served by the API you are talking to.",
        "security": [],
        "x-sdk": false,
        "responses": {
          "200": {
            "description": "The OpenAPI 3.1 document.",
            "content": {
              "application/yaml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/openapi.json": {
      "get": {
        "tags": [
          "System"
        ],
        "operationId": "getOpenAPIJSON",
        "summary": "OpenAPI specification (JSON)",
        "description": "This document as JSON.",
        "security": [],
        "x-sdk": false,
        "responses": {
          "200": {
            "description": "The OpenAPI 3.1 document.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/auth/console/register": {
      "post": {
        "tags": [
          "Console"
        ],
        "operationId": "consoleRegister",
        "summary": "Sign up",
        "description": "Self-service signup: provisions a new tenant and its first admin user,\nthen signs in. New tenants get the signup credit and the default\nsignals. Returns `403` when the deployment disables registration.\n",
        "security": [],
        "x-sdk": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RegisterRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Account created and signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Token"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/auth/console/login": {
      "post": {
        "tags": [
          "Console"
        ],
        "operationId": "consoleLogin",
        "summary": "Sign in",
        "description": "Verifies an email and password and returns a console token.\n\nFor an account with [two-factor authentication](#tag/Console/operation/getMfa)\non, a request without `code` gets `401` with the code `mfa_required`;\nrepeat it with a code from the authenticator app, or an unused recovery\ncode. Repeated failures lock the account and the caller's address for 15\nminutes: the API answers `429` with `Retry-After`.\n",
        "security": [],
        "x-sdk": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LoginRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Token"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/auth/token": {
      "post": {
        "tags": [
          "Console"
        ],
        "operationId": "exchangeToken",
        "summary": "Exchange a legacy key for a token",
        "deprecated": true,
        "description": "**Deprecated.** Exchanges a legacy API key and secret for a short-lived\ntenant token. Integrations now sign each request themselves; this path\nis kept for one release and logs a warning on every use.\n",
        "security": [],
        "x-sdk": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TokenExchangeRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A tenant token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Token"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/auth/console/password/forgot": {
      "post": {
        "tags": [
          "Console"
        ],
        "operationId": "forgotPassword",
        "summary": "Request a password reset",
        "description": "Emails a single-use reset link valid for one hour. It answers `202`\nwith the same body whether or not the email has an account, and is\nthrottled per client IP and per email.\n",
        "security": [],
        "x-sdk": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ForgotPasswordRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Message"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/auth/console/password/reset": {
      "post": {
        "tags": [
          "Console"
        ],
        "operationId": "resetPassword",
        "summary": "Reset a password",
        "description": "Sets a new password with a reset token. Every existing session is signed out.",
        "security": [],
        "x-sdk": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ResetPasswordRequest"
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Password reset."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        }
      }
    },
    "/auth/console/oauth/providers": {
      "get": {
        "tags": [
          "Console"
        ],
        "operationId": "listOAuthProviders",
        "summary": "List social sign-in providers",
        "security": [],
        "x-sdk": false,
        "responses": {
          "200": {
            "description": "The providers configured on this deployment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthProviders"
                }
              }
            }
          }
        }
      }
    },
    "/auth/console/oauth/{provider}/authorize": {
      "post": {
        "tags": [
          "Console"
        ],
        "operationId": "authorizeOAuth",
        "summary": "Get a provider authorization URL",
        "description": "Returns the provider's authorization URL for a `state` and a PKCE S256 `code_challenge`.",
        "security": [],
        "x-sdk": false,
        "parameters": [
          {
            "$ref": "#/components/parameters/Provider"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OAuthAuthorizeRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The URL to send the browser to.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthorizationURL"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/auth/console/oauth/{provider}": {
      "post": {
        "tags": [
          "Console"
        ],
        "operationId": "oauthLogin",
        "summary": "Sign in with a provider",
        "description": "Completes a social sign-in: exchanges the authorization code and PKCE\nverifier, then signs in the linked user, links a verified email to an\nexisting user, or signs up a new tenant (`201`, `new_user: true`).\n",
        "security": [],
        "x-sdk": false,
        "parameters": [
          {
            "$ref": "#/components/parameters/Provider"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OAuthCodeRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Signed in to an existing account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Token"
                }
              }
            }
          },
          "201": {
            "description": "A new account was created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Token"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          }
        }
      }
    },
    "/webhooks/stripe": {
      "post": {
        "tags": [
          "System"
        ],
        "operationId": "stripeWebhook",
        "summary": "Stripe events",
        "description": "Receives Stripe events, verified against the `Stripe-Signature` header.\nA paid top-up Checkout Session is credited to the tenant exactly once.\nCalled by Stripe, not by integrations.\n",
        "security": [],
        "x-sdk": false,
        "parameters": [
          {
            "name": "Stripe-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Received.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StripeWebhookAck"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/v1/me": {
      "get": {
        "tags": [
          "Account"
        ],
        "operationId": "getMe",
        "summary": "Get the current principal",
        "description": "Returns the tenant the credential belongs to. For console tokens it\nalso returns the signed-in user and whether they finished onboarding.\n",
        "x-sdk-resource": "me",
        "x-sdk-method": "get",
        "responses": {
          "200": {
            "description": "The authenticated principal.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Me"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/me/onboarded": {
      "post": {
        "tags": [
          "Console"
        ],
        "operationId": "setOnboarded",
        "summary": "Mark onboarding complete",
        "security": [
          {
            "ConsoleToken": []
          }
        ],
        "x-sdk": false,
        "responses": {
          "204": {
            "description": "Marked onboarded."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/me/password": {
      "post": {
        "tags": [
          "Console"
        ],
        "operationId": "changePassword",
        "summary": "Change password",
        "description": "Changes the signed-in user's password, signs out every other session, and returns a new console token.",
        "security": [
          {
            "ConsoleToken": []
          }
        ],
        "x-sdk": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChangePasswordRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Password changed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Token"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/v1/me/mfa": {
      "get": {
        "tags": [
          "Console"
        ],
        "operationId": "getMfa",
        "summary": "Get two-factor status",
        "description": "Whether two-factor authentication is on for the signed-in user, and how many recovery codes are left.",
        "security": [
          {
            "ConsoleToken": []
          }
        ],
        "x-sdk": false,
        "responses": {
          "200": {
            "description": "The two-factor status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MfaStatus"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/v1/me/mfa/setup": {
      "post": {
        "tags": [
          "Console"
        ],
        "operationId": "setupMfa",
        "summary": "Start two-factor setup",
        "description": "Issues a new secret to add to an authenticator app. Two-factor stays off\nuntil `POST /v1/me/mfa/enable` confirms a code from it. Calling this\nagain before enabling replaces the secret. Returns `409` when\ntwo-factor is already on.\n",
        "security": [
          {
            "ConsoleToken": []
          }
        ],
        "x-sdk": false,
        "responses": {
          "200": {
            "description": "The secret, as text and as an `otpauth://` URI for a QR code.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MfaSetup"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/v1/me/mfa/enable": {
      "post": {
        "tags": [
          "Console"
        ],
        "operationId": "enableMfa",
        "summary": "Turn on two-factor",
        "description": "Confirms the secret from setup with a current code and turns two-factor\non. Returns eight single-use recovery codes, **once**; each signs in\nonce if the authenticator is lost.\n",
        "security": [
          {
            "ConsoleToken": []
          }
        ],
        "x-sdk": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MfaEnableRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Two-factor is on.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MfaRecoveryCodes"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/v1/me/mfa/disable": {
      "post": {
        "tags": [
          "Console"
        ],
        "operationId": "disableMfa",
        "summary": "Turn off two-factor",
        "description": "Needs the account password (when it has one) and a current code or an\nunused recovery code, so a stolen session alone can't remove it.\n",
        "security": [
          {
            "ConsoleToken": []
          }
        ],
        "x-sdk": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MfaDisableRequest"
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Two-factor is off."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/v1/me/identities": {
      "get": {
        "tags": [
          "Console"
        ],
        "operationId": "listIdentities",
        "summary": "List sign-in methods",
        "security": [
          {
            "ConsoleToken": []
          }
        ],
        "x-sdk": false,
        "responses": {
          "200": {
            "description": "Password status and linked providers.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Identities"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/v1/me/identities/{provider}": {
      "post": {
        "tags": [
          "Console"
        ],
        "operationId": "linkIdentity",
        "summary": "Link a provider account",
        "security": [
          {
            "ConsoleToken": []
          }
        ],
        "x-sdk": false,
        "parameters": [
          {
            "$ref": "#/components/parameters/Provider"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OAuthCodeRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Linked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Identity"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      },
      "delete": {
        "tags": [
          "Console"
        ],
        "operationId": "unlinkIdentity",
        "summary": "Unlink a provider account",
        "description": "Refused with `409` when it is the user's last sign-in method.",
        "security": [
          {
            "ConsoleToken": []
          }
        ],
        "x-sdk": false,
        "parameters": [
          {
            "$ref": "#/components/parameters/Provider"
          }
        ],
        "responses": {
          "204": {
            "description": "Unlinked."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/v1/me/notifications": {
      "get": {
        "tags": [
          "Console"
        ],
        "operationId": "getNotificationPreferences",
        "summary": "Get email preferences",
        "security": [
          {
            "ConsoleToken": []
          }
        ],
        "x-sdk": false,
        "responses": {
          "200": {
            "description": "Every account email and whether the user receives it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotificationPreferences"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "put": {
        "tags": [
          "Console"
        ],
        "operationId": "updateNotificationPreferences",
        "summary": "Update email preferences",
        "description": "Maps an email event to whether the user wants it, e.g.\n`{\"low_balance\": false}`. Events left out keep their setting. Critical\nemails can't be turned off.\n",
        "security": [
          {
            "ConsoleToken": []
          }
        ],
        "x-sdk": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/NotificationPreferencesUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated preferences.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotificationPreferences"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/v1/overview": {
      "get": {
        "tags": [
          "Objects"
        ],
        "operationId": "getOverview",
        "summary": "Get tenant overview",
        "description": "Tenant-wide totals and per-signal value distributions, computed with SQL aggregates.",
        "x-sdk-resource": "overview",
        "x-sdk-method": "get",
        "responses": {
          "200": {
            "description": "The overview.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Overview"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/analyze": {
      "post": {
        "tags": [
          "Objects"
        ],
        "operationId": "analyzeAllObjects",
        "summary": "Re-analyze every object",
        "description": "Schedules a re-analysis of every object the tenant has, typically after\nchanging signal configuration. Jobs run at bulk priority, behind live\ntraffic. Each analysis is billed.\n",
        "x-sdk-resource": "objects",
        "x-sdk-method": "analyzeAll",
        "responses": {
          "202": {
            "description": "Scheduled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnalyzeAllScheduled"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/objects": {
      "get": {
        "tags": [
          "Objects"
        ],
        "operationId": "listObjects",
        "summary": "List objects",
        "description": "Returns one page of objects with their latest analysis.\n\n`q` is either a substring of the object ID or display name, or a\nsignal query such as `is_scammer > 90 and trust_score < 1` (see\n[Search and conditions](https://sigwise.ai/docs/guides/queries)).\n",
        "x-sdk-resource": "objects",
        "x-sdk-method": "list",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "description": "Substring search, or a signal query like `is_scammer >= 90, buyer_intent = ready_to_buy`.",
            "schema": {
              "type": "string"
            },
            "example": "is_scammer >= 90"
          },
          {
            "name": "sort",
            "in": "query",
            "description": "`recent` (last seen first, the default), `flagged` (highest yes/no probability first) or `trust` (highest score first).",
            "schema": {
              "type": "string",
              "enum": [
                "recent",
                "flagged",
                "trust"
              ],
              "default": "recent"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 100
            }
          },
          {
            "name": "offset",
            "in": "query",
            "description": "Rows to skip. Prefer `cursor` for deep pages; offsets get slower the further they go.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "The `next_cursor` of the previous page. Pages by position rather\nthan offset, so every page is equally fast; `offset` is ignored when\nset. Keep `q` and `sort` the same across pages. Signal queries page\nby `offset` only.\n",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of objects.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ObjectList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/v1/objects/{object_id}": {
      "get": {
        "tags": [
          "Objects"
        ],
        "operationId": "getObject",
        "summary": "Get an object's analysis",
        "description": "Returns the latest answer for each signal computed for the object, plus\nthe configured signals that have no answer yet (`pending`).\n\nReads are cached for a few seconds; the `X-Cache` header says whether\nthis one was a `HIT` or a `MISS`. Ingesting events and completed\nanalyses invalidate the cache.\n",
        "x-sdk-resource": "objects",
        "x-sdk-method": "get",
        "parameters": [
          {
            "$ref": "#/components/parameters/ObjectId"
          }
        ],
        "responses": {
          "200": {
            "description": "The object's analysis.",
            "headers": {
              "X-Cache": {
                "description": "`HIT` when served from cache, `MISS` otherwise.",
                "schema": {
                  "type": "string",
                  "enum": [
                    "HIT",
                    "MISS"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ObjectAnalysis"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/objects/{object_id}/events": {
      "post": {
        "tags": [
          "Events"
        ],
        "operationId": "ingestEvents",
        "summary": "Ingest events",
        "description": "Records one or more events or messages about an object.\n\n**Asynchronous (default).** Returns `202` immediately and schedules a\ndebounced analysis: a burst of events for one object coalesces into a\nsingle analysis once activity settles. Read the answers with\n`GET /v1/objects/{object_id}` or receive them by webhook.\n\n**Synchronous (`wait: true`).** Blocks on the analyzer and returns the\nverdict inline with `200`, so you can approve, review or block content\nbefore publishing it. Restrict scoring to some signals with `signals`,\nand score the request's events in isolation with\n`include_history: false`.\n\nReturns `402` when the tenant's balance is empty. A synchronous request\nreturns `429` when the account already has the maximum number of\nsynchronous analyses in flight (8 by default), and `503` with\n`analyzer_busy` when the analyzer is throttling; the events are\nrecorded either way, so don't resend them: after `Retry-After`, request\nan analysis with `POST /v1/objects/{object_id}/analyze` and read the\nanswers with `GET /v1/objects/{object_id}` or by webhook.\n\n**Safe retries.** Send an `Idempotency-Key` header (any unique string,\nsuch as a UUID) and retry with the same key after a timeout or a `5xx`:\nthe events are recorded once. A repeat of an asynchronous request is\nanswered like the first and carries `Idempotent-Replayed: true`. A repeat\nof a synchronous request returns `409` without analyzing (or charging)\nagain; read the verdict with `GET /v1/objects/{object_id}`. Reusing a key\nfor a different object returns `422`. Keys are remembered for 24 hours.\n",
        "x-sdk-resource": "events",
        "x-sdk-method": "ingest",
        "parameters": [
          {
            "$ref": "#/components/parameters/ObjectId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IngestRequest"
              },
              "examples": {
                "async": {
                  "summary": "Asynchronous ingest",
                  "value": {
                    "object_type": "user",
                    "events": [
                      {
                        "type": "event",
                        "name": "profile.updated",
                        "metadata": {
                          "field": "bio"
                        }
                      },
                      {
                        "type": "message",
                        "content": "is this still available? can I pay by wire?"
                      }
                    ]
                  }
                },
                "sync": {
                  "summary": "Direct moderation",
                  "value": {
                    "wait": true,
                    "signals": [
                      "is_scammer"
                    ],
                    "events": [
                      {
                        "type": "message",
                        "content": "pay me by wire and I double it"
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "`wait: true`: the events were recorded and the object scored.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ModerationVerdict"
                }
              }
            }
          },
          "202": {
            "description": "The events were recorded and an analysis scheduled.",
            "headers": {
              "Idempotent-Replayed": {
                "description": "`true` when this was a retry of an earlier request with the same `Idempotency-Key`; nothing was recorded again.",
                "schema": {
                  "type": "string",
                  "enum": [
                    "true"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IngestAccepted"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/IdempotencyKeyReused"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          },
          "503": {
            "$ref": "#/components/responses/AnalyzerBusy"
          }
        }
      },
      "get": {
        "tags": [
          "Events"
        ],
        "operationId": "listObjectEvents",
        "summary": "List an object's events",
        "description": "Returns up to the 200 most recent events and messages recorded for the object, newest first.",
        "x-sdk-resource": "events",
        "x-sdk-method": "list",
        "parameters": [
          {
            "$ref": "#/components/parameters/ObjectId"
          }
        ],
        "responses": {
          "200": {
            "description": "The object's events.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EventList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/objects/{object_id}/ingest": {
      "post": {
        "tags": [
          "Events"
        ],
        "operationId": "ingestEventsAlias",
        "summary": "Ingest events (alias)",
        "deprecated": true,
        "description": "An alias of `POST /v1/objects/{object_id}/events`. Use that path instead.",
        "x-sdk": false,
        "parameters": [
          {
            "$ref": "#/components/parameters/ObjectId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IngestRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "`wait: true`: the events were recorded and the object scored.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ModerationVerdict"
                }
              }
            }
          },
          "202": {
            "description": "The events were recorded and an analysis scheduled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IngestAccepted"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          }
        }
      }
    },
    "/v1/objects/{object_id}/state": {
      "get": {
        "tags": [
          "Objects"
        ],
        "operationId": "getObjectState",
        "summary": "Get an object's compacted history",
        "description": "Older events are folded into a rolling summary so the analyzer gets a\nbounded payload while keeping long-term signal. This returns that\nsummary and the compaction cursor.\n",
        "x-sdk-resource": "objects",
        "x-sdk-method": "getState",
        "parameters": [
          {
            "$ref": "#/components/parameters/ObjectId"
          }
        ],
        "responses": {
          "200": {
            "description": "The compacted state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ObjectState"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/objects/{object_id}/analyze": {
      "post": {
        "tags": [
          "Objects"
        ],
        "operationId": "analyzeObject",
        "summary": "Re-analyze an object",
        "description": "Schedules an immediate re-analysis without a new event, for example\nright after adding a signal. Runs at live priority; poll\n`GET /v1/objects/{object_id}` for the result. Returns `404` when the\nobject has no events.\n",
        "x-sdk-resource": "objects",
        "x-sdk-method": "analyze",
        "parameters": [
          {
            "$ref": "#/components/parameters/ObjectId"
          }
        ],
        "responses": {
          "202": {
            "description": "Scheduled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnalyzeScheduled"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/signals": {
      "get": {
        "tags": [
          "Signals"
        ],
        "operationId": "listSignals",
        "summary": "List signals",
        "x-sdk-resource": "signals",
        "x-sdk-method": "list",
        "responses": {
          "200": {
            "description": "Every configured signal, enabled or not.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SignalList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/signals/{key}": {
      "put": {
        "tags": [
          "Signals"
        ],
        "operationId": "upsertSignal",
        "summary": "Create or update a signal",
        "description": "Creates the signal, or replaces its definition. `instructions` and\n`criteria` are passed to the analyzer verbatim; the shape of\n`criteria` depends on `type`:\n\n| `type`   | `criteria`                                           |\n|----------|------------------------------------------------------|\n| `noul`   | object with optional `true` / `false` descriptions   |\n| `score`  | ordered array of level descriptions, low to high     |\n| `choice` | object mapping each option to a description or null |\n\nCreating a signal does not analyze existing objects unless the tenant\nsetting `auto_backfill_signals` is on; start a backfill explicitly with\n`POST /v1/signals/{key}/backfill`.\n",
        "x-sdk-resource": "signals",
        "x-sdk-method": "upsert",
        "parameters": [
          {
            "$ref": "#/components/parameters/SignalKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SignalInput"
              },
              "examples": {
                "noul": {
                  "summary": "Yes/no probability",
                  "value": {
                    "type": "noul",
                    "instructions": "Decide if this user is likely a scammer.",
                    "criteria": {
                      "true": "clear scam signals",
                      "false": "legitimate behaviour"
                    }
                  }
                },
                "score": {
                  "summary": "Labelled spectrum",
                  "value": {
                    "type": "score",
                    "instructions": "Rate overall trustworthiness.",
                    "criteria": [
                      "high risk",
                      "neutral",
                      "trusted"
                    ]
                  }
                },
                "choice": {
                  "summary": "One of N",
                  "value": {
                    "type": "choice",
                    "instructions": "Classify buyer intent.",
                    "criteria": {
                      "browsing": null,
                      "comparing": null,
                      "ready_to_buy": null
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The saved signal.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Signal"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "delete": {
        "tags": [
          "Signals"
        ],
        "operationId": "deleteSignal",
        "summary": "Delete a signal",
        "x-sdk-resource": "signals",
        "x-sdk-method": "delete",
        "parameters": [
          {
            "$ref": "#/components/parameters/SignalKey"
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/signals/{key}/backfill": {
      "get": {
        "tags": [
          "Signals"
        ],
        "operationId": "getSignalBackfill",
        "summary": "Get backfill estimate and progress",
        "description": "Returns how many existing objects have no answer for the signal and\nwhat analyzing them would cost now, plus the progress of the most\nrecent backfill (`null` if none was started).\n",
        "x-sdk-resource": "signals",
        "x-sdk-method": "getBackfill",
        "parameters": [
          {
            "$ref": "#/components/parameters/SignalKey"
          }
        ],
        "responses": {
          "200": {
            "description": "Estimate and progress.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BackfillStatus"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "tags": [
          "Signals"
        ],
        "operationId": "startSignalBackfill",
        "summary": "Start a backfill",
        "description": "Re-analyzes, for this signal only, every existing object that has no\nanswer for it. Jobs run at bulk priority so they never delay live\ningest. Each analysis is billed. Returns `409` if the signal is\ndisabled.\n",
        "x-sdk-resource": "signals",
        "x-sdk-method": "startBackfill",
        "parameters": [
          {
            "$ref": "#/components/parameters/SignalKey"
          }
        ],
        "responses": {
          "202": {
            "description": "Scheduled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SignalBackfill"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/v1/settings": {
      "get": {
        "tags": [
          "Account"
        ],
        "operationId": "getSettings",
        "summary": "Get tenant settings",
        "x-sdk-resource": "settings",
        "x-sdk-method": "get",
        "responses": {
          "200": {
            "description": "The settings.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Settings"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "patch": {
        "tags": [
          "Account"
        ],
        "operationId": "updateSettings",
        "summary": "Update tenant settings",
        "description": "Omitted fields are left unchanged.",
        "x-sdk-resource": "settings",
        "x-sdk-method": "update",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SettingsUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated settings.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Settings"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/webhooks": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "listWebhooks",
        "summary": "List webhook endpoints",
        "x-sdk-resource": "webhooks",
        "x-sdk-method": "list",
        "responses": {
          "200": {
            "description": "The endpoints, without their secrets.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEndpointList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "post": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "createWebhook",
        "summary": "Register a webhook endpoint",
        "description": "Registers an endpoint and returns its signing secret. **The secret is\nshown only once**; store it to verify deliveries.\n",
        "x-sdk-resource": "webhooks",
        "x-sdk-method": "create",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEndpointCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Registered.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEndpointCreated"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/webhooks/{endpoint_id}": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "getWebhook",
        "summary": "Get a webhook endpoint",
        "x-sdk-resource": "webhooks",
        "x-sdk-method": "get",
        "parameters": [
          {
            "$ref": "#/components/parameters/EndpointId"
          }
        ],
        "responses": {
          "200": {
            "description": "The endpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEndpoint"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "updateWebhook",
        "summary": "Update a webhook endpoint",
        "description": "Omitted fields are left unchanged.",
        "x-sdk-resource": "webhooks",
        "x-sdk-method": "update",
        "parameters": [
          {
            "$ref": "#/components/parameters/EndpointId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEndpointUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated endpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEndpoint"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "deleteWebhook",
        "summary": "Delete a webhook endpoint",
        "description": "Removes the endpoint and its deliveries.",
        "x-sdk-resource": "webhooks",
        "x-sdk-method": "delete",
        "parameters": [
          {
            "$ref": "#/components/parameters/EndpointId"
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/webhook_deliveries": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "listWebhookDeliveries",
        "summary": "List webhook deliveries",
        "description": "Recent deliveries, newest first. Filter with `status=dead` to inspect the dead-letter queue.",
        "x-sdk-resource": "webhookDeliveries",
        "x-sdk-method": "list",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/DeliveryStatus"
            }
          },
          {
            "name": "endpoint_id",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The deliveries.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookDeliveryList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/webhook_deliveries/{delivery_id}/replay": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "replayWebhookDelivery",
        "summary": "Replay a delivery",
        "description": "Re-queues a delivery (for example one in the dead-letter queue) for another attempt.",
        "x-sdk-resource": "webhookDeliveries",
        "x-sdk-method": "replay",
        "parameters": [
          {
            "$ref": "#/components/parameters/DeliveryId"
          }
        ],
        "responses": {
          "202": {
            "description": "Re-queued.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookDelivery"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/rules": {
      "get": {
        "tags": [
          "Rules"
        ],
        "operationId": "listRules",
        "summary": "List rules",
        "x-sdk-resource": "rules",
        "x-sdk-method": "list",
        "responses": {
          "200": {
            "description": "The rules.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RuleList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "post": {
        "tags": [
          "Rules"
        ],
        "operationId": "createRule",
        "summary": "Create a rule",
        "description": "The condition is validated against the tenant's signals, and a\n`webhook` action must reference a registered endpoint subscribed to\n`rule.triggered`.\n",
        "x-sdk-resource": "rules",
        "x-sdk-method": "create",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RuleCreate"
              },
              "example": {
                "name": "scammer alert",
                "when": "is_scammer >= 90 and trust_score < 1",
                "action_type": "slack",
                "action_config": {
                  "webhook_url": "https://hooks.slack.com/services/T000/B000/XXXX"
                },
                "cooldown_seconds": 3600
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Rule"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/rules/{rule_id}": {
      "get": {
        "tags": [
          "Rules"
        ],
        "operationId": "getRule",
        "summary": "Get a rule",
        "x-sdk-resource": "rules",
        "x-sdk-method": "get",
        "parameters": [
          {
            "$ref": "#/components/parameters/RuleId"
          }
        ],
        "responses": {
          "200": {
            "description": "The rule.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Rule"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "tags": [
          "Rules"
        ],
        "operationId": "updateRule",
        "summary": "Update a rule",
        "description": "Omitted fields are left unchanged. The resulting condition and action are re-validated.",
        "x-sdk-resource": "rules",
        "x-sdk-method": "update",
        "parameters": [
          {
            "$ref": "#/components/parameters/RuleId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RuleUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated rule.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Rule"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "tags": [
          "Rules"
        ],
        "operationId": "deleteRule",
        "summary": "Delete a rule",
        "x-sdk-resource": "rules",
        "x-sdk-method": "delete",
        "parameters": [
          {
            "$ref": "#/components/parameters/RuleId"
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/rule_firings": {
      "get": {
        "tags": [
          "Rules"
        ],
        "operationId": "listRuleFirings",
        "summary": "List rule firings",
        "description": "The audit log of rule firings and their action outcomes, newest first.",
        "x-sdk-resource": "ruleFirings",
        "x-sdk-method": "list",
        "parameters": [
          {
            "name": "rule_id",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "object_id",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The firings.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RuleFiringList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/api_keys": {
      "get": {
        "tags": [
          "API keys"
        ],
        "operationId": "listApiKeys",
        "summary": "List API keys",
        "description": "Revoked keys are not listed. Secrets are never returned.",
        "x-sdk-resource": "apiKeys",
        "x-sdk-method": "list",
        "responses": {
          "200": {
            "description": "The keys.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIKeyList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "post": {
        "tags": [
          "API keys"
        ],
        "operationId": "createApiKey",
        "summary": "Create an API key",
        "description": "Mints a key ID and signing secret. **The secret is shown only once.**",
        "x-sdk-resource": "apiKeys",
        "x-sdk-method": "create",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/APIKeyCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIKeySecret"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/api_keys/{key_id}": {
      "patch": {
        "tags": [
          "API keys"
        ],
        "operationId": "updateApiKey",
        "summary": "Update an API key",
        "description": "Rename a key, or deactivate and reactivate it. Use `DELETE` to revoke.",
        "x-sdk-resource": "apiKeys",
        "x-sdk-method": "update",
        "parameters": [
          {
            "$ref": "#/components/parameters/KeyId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/APIKeyUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIKey"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "tags": [
          "API keys"
        ],
        "operationId": "deleteApiKey",
        "summary": "Revoke an API key",
        "description": "The key stops authenticating immediately. Its ID is never reissued.",
        "x-sdk-resource": "apiKeys",
        "x-sdk-method": "revoke",
        "parameters": [
          {
            "$ref": "#/components/parameters/KeyId"
          }
        ],
        "responses": {
          "204": {
            "description": "Revoked."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/api_keys/{key_id}/rotate": {
      "post": {
        "tags": [
          "API keys"
        ],
        "operationId": "rotateApiKey",
        "summary": "Rotate an API key's secret",
        "description": "Keeps the key ID and replaces the secret. The old secret stops working\nimmediately; the new one is returned only once.\n",
        "x-sdk-resource": "apiKeys",
        "x-sdk-method": "rotate",
        "parameters": [
          {
            "$ref": "#/components/parameters/KeyId"
          }
        ],
        "responses": {
          "200": {
            "description": "Rotated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIKeySecret"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/billing": {
      "get": {
        "tags": [
          "Billing"
        ],
        "operationId": "getBalance",
        "summary": "Get the prepaid balance",
        "x-sdk-resource": "billing",
        "x-sdk-method": "getBalance",
        "responses": {
          "200": {
            "description": "The balance and billing configuration.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Balance"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/billing/ledger": {
      "get": {
        "tags": [
          "Billing"
        ],
        "operationId": "listLedger",
        "summary": "List ledger entries",
        "description": "One page of the billing ledger, newest first. Pass the previous page's\n`next_cursor` as `cursor` to continue; it is empty on the last page.\n",
        "x-sdk-resource": "billing",
        "x-sdk-method": "listLedger",
        "parameters": [
          {
            "name": "type",
            "in": "query",
            "description": "Only credits (funds added) or only charges (analyses).",
            "schema": {
              "type": "string",
              "enum": [
                "credit",
                "charge"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of entries.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LedgerPage"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/billing/checkout": {
      "post": {
        "tags": [
          "Billing"
        ],
        "operationId": "createCheckout",
        "summary": "Start a card top-up",
        "description": "Creates a hosted Stripe Checkout Session for the amount and returns its\nURL. The balance is credited only once Stripe confirms the payment.\nReturns `503` when card payments are not configured.\n",
        "x-sdk-resource": "billing",
        "x-sdk-method": "createCheckout",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CheckoutCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The Checkout Session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CheckoutSession"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/v1/billing/checkout/{session_id}": {
      "get": {
        "tags": [
          "Billing"
        ],
        "operationId": "getCheckout",
        "summary": "Get a top-up's status",
        "description": "Whether the Checkout Session's payment has been credited to the balance yet.",
        "x-sdk-resource": "billing",
        "x-sdk-method": "getCheckout",
        "parameters": [
          {
            "name": "session_id",
            "in": "path",
            "required": true,
            "description": "The Checkout Session ID (`cs_…`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The top-up's status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CheckoutStatus"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/usage": {
      "get": {
        "tags": [
          "Usage"
        ],
        "operationId": "getUsage",
        "summary": "Get usage",
        "description": "Analyses, spend and tokens over an inclusive UTC date range (at most\n366 days), with a zero-filled daily series and breakdowns by model,\nsource (`async` / `sync`) and signal. Defaults to the last 30 days.\n",
        "x-sdk-resource": "usage",
        "x-sdk-method": "get",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "description": "First day, `YYYY-MM-DD`. Defaults to 29 days before `to`.",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "Last day, `YYYY-MM-DD`. Defaults to today.",
            "schema": {
              "type": "string",
              "format": "date"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Usage for the range.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Usage"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    }
  },
  "webhooks": {
    "analysis.completed": {
      "post": {
        "operationId": "onAnalysisCompleted",
        "summary": "Analysis completed",
        "description": "Sent to every enabled endpoint subscribed to `analysis.completed`\n(whose `signal_keys` filter matches) each time an asynchronous analysis\nfinishes. Verify\n`X-Webhook-Signature` before trusting the body.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookSignature"
          },
          {
            "$ref": "#/components/parameters/WebhookTimestamp"
          },
          {
            "$ref": "#/components/parameters/WebhookEvent"
          },
          {
            "$ref": "#/components/parameters/WebhookDelivery"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AnalysisCompletedEvent"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Any `2xx` acknowledges the delivery. `429` and `5xx` are retried; other `4xx` are not."
          }
        }
      }
    },
    "rule.triggered": {
      "post": {
        "operationId": "onRuleTriggered",
        "summary": "Rule triggered",
        "description": "Sent to the endpoint a `webhook` rule action references when the rule\nfires. The endpoint must be enabled and subscribed to `rule.triggered`;\notherwise the firing is recorded with status `error`.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookSignature"
          },
          {
            "$ref": "#/components/parameters/WebhookTimestamp"
          },
          {
            "$ref": "#/components/parameters/WebhookEvent"
          },
          {
            "$ref": "#/components/parameters/WebhookDelivery"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RuleTriggeredEvent"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Any `2xx` acknowledges the delivery."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyId": {
        "type": "apiKey",
        "in": "query",
        "name": "api_key",
        "description": "The public key ID. It identifies the key but grants nothing\non its own: the request must also carry a JWT signed with the key's\nsecret. It may instead be sent as the JWT header `kid`.\n"
      },
      "SignedJWT": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "An HS256 JWT signed with the API key's secret, sent as\n`Authorization: Bearer <jwt>`.\n\n| Claim | Required | Meaning |\n|-------|----------|---------|\n| `iat` | yes | Issued at, Unix seconds |\n| `exp` | yes | Expiry, at most 300 s after `iat` |\n| `jti` | no  | A unique token ID |\n| `htm` | no  | Binds the token to one HTTP method, e.g. `POST` |\n| `htu` | no  | Binds the token to one path, e.g. `/v1/objects/user-42` |\n\n±60 s of clock skew is allowed. Any failure is a generic `401`.\n"
      },
      "ConsoleToken": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "A console token minted by `POST /auth/console/login`, used by the\nSigWise console on behalf of a signed-in user. Requests with a console\ntoken carry no `api_key`.\n"
      }
    },
    "parameters": {
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "description": "A unique string (such as a UUID) that makes the request safe to retry:\nrepeating it with the same key records nothing again. At most 255\ncharacters; remembered for 24 hours.\n",
        "schema": {
          "type": "string",
          "maxLength": 255
        },
        "example": "6f1c0e3e-5b8a-4c7e-9f2d-1a2b3c4d5e6f"
      },
      "ObjectId": {
        "name": "object_id",
        "in": "path",
        "required": true,
        "description": "Your identifier for the object, e.g. `user-42`.",
        "schema": {
          "type": "string"
        },
        "example": "user-42"
      },
      "SignalKey": {
        "name": "key",
        "in": "path",
        "required": true,
        "description": "The signal's key, e.g. `is_scammer`.",
        "schema": {
          "type": "string",
          "pattern": "^[a-zA-Z_][a-zA-Z0-9_]*$"
        },
        "example": "is_scammer"
      },
      "EndpointId": {
        "name": "endpoint_id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "DeliveryId": {
        "name": "delivery_id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "RuleId": {
        "name": "rule_id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "KeyId": {
        "name": "key_id",
        "in": "path",
        "required": true,
        "description": "The key's `id` (a UUID), not its public `your_key_id` key ID.",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "Provider": {
        "name": "provider",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "enum": [
            "google",
            "github"
          ]
        }
      },
      "WebhookSignature": {
        "name": "X-Webhook-Signature",
        "in": "header",
        "required": true,
        "description": "`sha256=<hex>`: HMAC-SHA256 of `\"<timestamp>.<raw body>\"` keyed with the endpoint secret.",
        "schema": {
          "type": "string"
        }
      },
      "WebhookTimestamp": {
        "name": "X-Webhook-Timestamp",
        "in": "header",
        "required": true,
        "description": "Unix seconds when the attempt was signed. Reject stale values to prevent replay.",
        "schema": {
          "type": "string"
        }
      },
      "WebhookEvent": {
        "name": "X-Webhook-Event",
        "in": "header",
        "required": true,
        "schema": {
          "type": "string",
          "enum": [
            "analysis.completed",
            "rule.triggered"
          ]
        }
      },
      "WebhookDelivery": {
        "name": "X-Webhook-Delivery",
        "in": "header",
        "description": "The delivery ID. Retries of one delivery share it, so use it to deduplicate.",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "The request is malformed or fails validation.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "bad_request",
                "message": "at least one event is required"
              }
            }
          }
        }
      },
      "Unauthorized": {
        "description": "The credential is missing, invalid, expired or revoked.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "unauthorized",
                "message": "unauthorized"
              }
            }
          }
        }
      },
      "PaymentRequired": {
        "description": "The tenant's prepaid balance is empty.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "payment_required",
                "message": "insufficient balance"
              }
            }
          }
        }
      },
      "Forbidden": {
        "description": "The credential is valid but cannot perform this action.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "forbidden",
                "message": "this is a console-user action"
              }
            }
          }
        }
      },
      "NotFound": {
        "description": "The resource does not exist.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "not_found",
                "message": "signal not found"
              }
            }
          }
        }
      },
      "IdempotencyKeyReused": {
        "description": "The `Idempotency-Key` was already used for a different object.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "idempotency_key_reused",
                "message": "idempotency key already used for a different object"
              }
            }
          }
        }
      },
      "Conflict": {
        "description": "The request conflicts with the resource's current state.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "conflict",
                "message": "signal is disabled; enable it before re-analyzing"
              }
            }
          }
        }
      },
      "TooManyRequests": {
        "description": "Too many requests or attempts; retry after the `Retry-After` delay.",
        "headers": {
          "Retry-After": {
            "description": "Seconds to wait before retrying.",
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "rate_limited",
                "message": "too many attempts; try again later"
              }
            }
          }
        }
      },
      "AnalyzerBusy": {
        "description": "The analyzer is throttling requests. The events were recorded; retry the verdict after the `Retry-After` delay.",
        "headers": {
          "Retry-After": {
            "description": "Seconds to wait before retrying.",
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "analyzer_busy",
                "message": "analyzer is busy; retry after the Retry-After delay"
              }
            }
          }
        }
      },
      "BadGateway": {
        "description": "An upstream service (the analyzer, Stripe, an OAuth provider) failed.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "internal_error",
                "message": "analysis failed"
              }
            }
          }
        }
      },
      "ServiceUnavailable": {
        "description": "The feature is not configured on this deployment.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "payments_not_configured",
                "message": "self-serve payments are not configured"
              }
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "$ref": "#/components/schemas/ErrorBody"
          }
        }
      },
      "ErrorBody": {
        "type": "object",
        "required": [
          "code",
          "message"
        ],
        "properties": {
          "code": {
            "type": "string",
            "description": "A stable, machine-readable code: `bad_request`, `unauthorized`,\n`forbidden`, `not_found`, `conflict`, `payment_required`,\n`rate_limited`, `payments_not_configured`,\n`payment_provider_error`, `internal_error`, `not_ready`, or a\nconsole-specific code such as `weak_password`.\n",
            "examples": [
              "not_found"
            ]
          },
          "message": {
            "type": "string",
            "description": "A human-readable explanation. Do not match on it; it may change.",
            "examples": [
              "signal not found"
            ]
          }
        }
      },
      "Health": {
        "type": "object",
        "required": [
          "status"
        ],
        "properties": {
          "status": {
            "type": "string",
            "examples": [
              "ok"
            ]
          }
        }
      },
      "Message": {
        "type": "object",
        "required": [
          "message"
        ],
        "properties": {
          "message": {
            "type": "string"
          }
        }
      },
      "StripeWebhookAck": {
        "type": "object",
        "required": [
          "received"
        ],
        "properties": {
          "received": {
            "type": "boolean"
          }
        }
      },
      "Metadata": {
        "type": "object",
        "description": "Arbitrary structured attributes.",
        "additionalProperties": true
      },
      "EventType": {
        "type": "string",
        "enum": [
          "event",
          "message"
        ],
        "description": "`event` is a discrete action (e.g. `profile.updated`); `message` is free-form text."
      },
      "EventInput": {
        "type": "object",
        "required": [
          "type"
        ],
        "properties": {
          "type": {
            "$ref": "#/components/schemas/EventType"
          },
          "name": {
            "type": "string",
            "description": "The event name, or a message's subject.",
            "examples": [
              "profile.updated"
            ]
          },
          "content": {
            "type": "string",
            "description": "Free text, such as a message body."
          },
          "metadata": {
            "$ref": "#/components/schemas/Metadata"
          },
          "occurred_at": {
            "type": "string",
            "format": "date-time",
            "description": "When it happened. Defaults to the time the API received it."
          }
        }
      },
      "IngestRequest": {
        "type": "object",
        "required": [
          "events"
        ],
        "properties": {
          "object_type": {
            "type": "string",
            "description": "Optional classification, e.g. `user` or `listing`.",
            "examples": [
              "user"
            ]
          },
          "events": {
            "type": "array",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/EventInput"
            }
          },
          "wait": {
            "type": "boolean",
            "default": false,
            "description": "Score the object inline and return the verdict (`200`) instead of scheduling analysis (`202`)."
          },
          "signals": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "With `wait`, score only these signal keys. Ignored otherwise."
          },
          "include_history": {
            "type": "boolean",
            "default": true,
            "description": "With `wait`, whether to score over the object's prior history (`true`) or only this request's events (`false`)."
          }
        }
      },
      "IngestAccepted": {
        "type": "object",
        "required": [
          "object_id",
          "accepted",
          "analysis_status",
          "analysis_delay_ms"
        ],
        "properties": {
          "object_id": {
            "type": "string"
          },
          "accepted": {
            "type": "integer",
            "description": "How many events were recorded."
          },
          "analysis_status": {
            "type": "string",
            "enum": [
              "scheduled"
            ]
          },
          "analysis_delay_ms": {
            "type": "integer",
            "description": "The debounce window before the analysis runs."
          }
        }
      },
      "Answer": {
        "type": "object",
        "description": "One signal's answer. Only the fields for its `type` are set.",
        "required": [
          "signal",
          "type"
        ],
        "properties": {
          "signal": {
            "type": "string",
            "examples": [
              "is_scammer"
            ]
          },
          "type": {
            "$ref": "#/components/schemas/SignalType"
          },
          "noul": {
            "type": "number",
            "minimum": 0,
            "maximum": 1,
            "description": "`noul`: the probability the answer is yes."
          },
          "choice": {
            "type": "string",
            "description": "`choice`: the most likely option."
          },
          "score": {
            "type": "number",
            "description": "`score`: the position on the spectrum, from 0 (the first level) to the number of levels minus one."
          },
          "probabilities": {
            "type": "object",
            "additionalProperties": {
              "type": "number"
            },
            "description": "`choice` and `score`: the probability of each option or level."
          },
          "confidence": {
            "type": "number",
            "description": "`choice` and `score`: the analyzer's confidence."
          }
        }
      },
      "ModerationVerdict": {
        "type": "object",
        "required": [
          "object_id",
          "accepted",
          "analyzed",
          "history_included",
          "model",
          "latency_ms",
          "answers"
        ],
        "properties": {
          "object_id": {
            "type": "string"
          },
          "accepted": {
            "type": "integer",
            "description": "How many events were recorded."
          },
          "analyzed": {
            "type": "boolean",
            "description": "False when no configured signal matched, so nothing was scored."
          },
          "history_included": {
            "type": "boolean"
          },
          "model": {
            "type": "string",
            "description": "The model that produced the answers, e.g. `model-1`."
          },
          "latency_ms": {
            "type": "integer"
          },
          "answers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Answer"
            }
          },
          "reason": {
            "type": "string",
            "description": "Why nothing was analyzed, when `analyzed` is false."
          }
        }
      },
      "Event": {
        "type": "object",
        "required": [
          "type",
          "occurred_at"
        ],
        "properties": {
          "type": {
            "$ref": "#/components/schemas/EventType"
          },
          "name": {
            "type": "string"
          },
          "content": {
            "type": "string"
          },
          "metadata": {
            "$ref": "#/components/schemas/Metadata"
          },
          "occurred_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "EventList": {
        "type": "object",
        "required": [
          "events"
        ],
        "properties": {
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Event"
            }
          }
        }
      },
      "SignalResult": {
        "type": "object",
        "description": "The latest answer for one signal. Only the fields for its `type` are set.",
        "required": [
          "key",
          "type",
          "computed_at"
        ],
        "properties": {
          "key": {
            "type": "string",
            "examples": [
              "trust_score"
            ]
          },
          "type": {
            "$ref": "#/components/schemas/SignalType"
          },
          "noul": {
            "type": "number",
            "minimum": 0,
            "maximum": 1
          },
          "choice": {
            "type": "string"
          },
          "score": {
            "type": "number"
          },
          "probabilities": {
            "type": "object",
            "additionalProperties": {
              "type": "number"
            }
          },
          "confidence": {
            "type": "number"
          },
          "model": {
            "type": "string"
          },
          "computed_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ObjectAnalysis": {
        "type": "object",
        "required": [
          "object_id",
          "event_count",
          "analysis",
          "pending"
        ],
        "properties": {
          "object_id": {
            "type": "string",
            "examples": [
              "user-42"
            ]
          },
          "event_count": {
            "type": "integer"
          },
          "analysis": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SignalResult"
            }
          },
          "pending": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Enabled signals with no answer yet."
          }
        }
      },
      "ObjectSummary": {
        "type": "object",
        "required": [
          "object_id",
          "object_type",
          "event_count",
          "last_seen",
          "analysis",
          "pending"
        ],
        "properties": {
          "object_id": {
            "type": "string"
          },
          "object_type": {
            "type": "string"
          },
          "display_name": {
            "type": "string"
          },
          "event_count": {
            "type": "integer"
          },
          "last_seen": {
            "type": "string",
            "format": "date-time"
          },
          "analysis": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SignalResult"
            }
          },
          "pending": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "ObjectList": {
        "type": "object",
        "required": [
          "objects",
          "limit",
          "offset"
        ],
        "properties": {
          "objects": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ObjectSummary"
            }
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          },
          "next_cursor": {
            "type": "string",
            "description": "Pass as `cursor` to fetch the next page. Absent on the last page and for signal queries."
          }
        }
      },
      "CompactedState": {
        "type": "object",
        "description": "A rolling summary of an object's older events.",
        "required": [
          "total_events",
          "messages",
          "actions",
          "first_seen",
          "updated_at"
        ],
        "properties": {
          "total_events": {
            "type": "integer"
          },
          "messages": {
            "type": "integer"
          },
          "actions": {
            "type": "integer"
          },
          "by_action": {
            "type": "object",
            "additionalProperties": {
              "type": "integer"
            }
          },
          "sample_messages": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "first_seen": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ObjectState": {
        "type": "object",
        "required": [
          "object_id",
          "compacted_events"
        ],
        "properties": {
          "object_id": {
            "type": "string"
          },
          "compacted_events": {
            "type": "integer",
            "description": "How many events have been folded into the summary."
          },
          "compacted_through": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "compacted_state": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/CompactedState"
              },
              {
                "type": "null"
              }
            ]
          },
          "last_analyzed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "AnalyzeScheduled": {
        "type": "object",
        "required": [
          "object_id",
          "analysis_status"
        ],
        "properties": {
          "object_id": {
            "type": "string"
          },
          "analysis_status": {
            "type": "string",
            "enum": [
              "scheduled"
            ]
          }
        }
      },
      "AnalyzeAllScheduled": {
        "type": "object",
        "required": [
          "scheduled",
          "analysis_status"
        ],
        "properties": {
          "scheduled": {
            "type": "integer",
            "description": "How many objects were scheduled."
          },
          "analysis_status": {
            "type": "string",
            "enum": [
              "scheduled"
            ]
          }
        }
      },
      "Overview": {
        "type": "object",
        "required": [
          "objects",
          "signals",
          "analyzed_pct",
          "events_today",
          "flagged",
          "signal_summary",
          "categories",
          "top_risk"
        ],
        "properties": {
          "objects": {
            "type": "integer"
          },
          "signals": {
            "type": "integer"
          },
          "analyzed_pct": {
            "type": "number",
            "description": "Share of objects with at least one answer, 0–1."
          },
          "events_today": {
            "type": "integer",
            "description": "Events recorded in the last 24 hours."
          },
          "flagged": {
            "type": "integer"
          },
          "signal_summary": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SignalSummary"
            }
          },
          "categories": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CategoryCount"
            }
          },
          "top_risk": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RiskyObject"
            }
          }
        }
      },
      "CategoryCount": {
        "type": "object",
        "description": "How many objects have one `object_type`.",
        "required": [
          "name",
          "count"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "count": {
            "type": "integer"
          }
        }
      },
      "RiskyObject": {
        "type": "object",
        "description": "One of the objects with the highest yes/no probabilities.",
        "required": [
          "object_id",
          "risk"
        ],
        "properties": {
          "object_id": {
            "type": "string"
          },
          "display_name": {
            "type": "string"
          },
          "risk": {
            "type": "number"
          }
        }
      },
      "SignalSummary": {
        "type": "object",
        "required": [
          "key",
          "type",
          "total"
        ],
        "properties": {
          "key": {
            "type": "string"
          },
          "type": {
            "$ref": "#/components/schemas/SignalType"
          },
          "total": {
            "type": "integer",
            "description": "Objects with an answer for the signal."
          },
          "avg": {
            "type": "number"
          },
          "high": {
            "type": "integer",
            "description": "Objects answered high (yes/no signals)."
          },
          "high_pct": {
            "type": "number"
          },
          "choices": {
            "type": "object",
            "additionalProperties": {
              "type": "number"
            },
            "description": "`choice` signals: the share of each option."
          }
        }
      },
      "SignalType": {
        "type": "string",
        "enum": [
          "noul",
          "choice",
          "score"
        ],
        "description": "`noul`: yes/no probability. `score`: position on a labelled spectrum. `choice`: one of N options."
      },
      "SignalInput": {
        "type": "object",
        "required": [
          "type",
          "instructions"
        ],
        "properties": {
          "type": {
            "$ref": "#/components/schemas/SignalType"
          },
          "instructions": {
            "description": "What the analyzer should decide. Usually a string; objects and arrays are passed through verbatim.",
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "object"
              },
              {
                "type": "array"
              }
            ]
          },
          "criteria": {
            "description": "Depends on `type`; see the operation description.",
            "oneOf": [
              {
                "type": "object"
              },
              {
                "type": "array"
              }
            ]
          },
          "enabled": {
            "type": "boolean",
            "default": true
          }
        }
      },
      "Signal": {
        "type": "object",
        "required": [
          "key",
          "type",
          "instructions",
          "enabled",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "key": {
            "type": "string",
            "examples": [
              "is_scammer"
            ]
          },
          "type": {
            "$ref": "#/components/schemas/SignalType"
          },
          "instructions": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "object"
              },
              {
                "type": "array"
              }
            ]
          },
          "criteria": {
            "oneOf": [
              {
                "type": "object"
              },
              {
                "type": "array"
              }
            ]
          },
          "enabled": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "backfill": {
            "$ref": "#/components/schemas/SignalBackfill",
            "description": "Set only when creating the signal started a backfill (`auto_backfill_signals`)."
          }
        }
      },
      "SignalList": {
        "type": "object",
        "required": [
          "signals"
        ],
        "properties": {
          "signals": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Signal"
            }
          }
        }
      },
      "SignalBackfill": {
        "type": "object",
        "required": [
          "signal_key",
          "status",
          "total",
          "completed",
          "pending",
          "started_at"
        ],
        "properties": {
          "signal_key": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "running",
              "done"
            ]
          },
          "total": {
            "type": "integer",
            "description": "Objects enqueued when the backfill started."
          },
          "completed": {
            "type": "integer",
            "description": "Objects answered since it started."
          },
          "pending": {
            "type": "integer",
            "description": "Objects still queued or running."
          },
          "started_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "BackfillEstimate": {
        "type": "object",
        "required": [
          "objects",
          "estimated_cost_micros"
        ],
        "properties": {
          "objects": {
            "type": "integer",
            "description": "Objects with no answer for the signal."
          },
          "estimated_cost_micros": {
            "type": "integer",
            "description": "Estimated cost in micro-dollars (1e-6 USD), an upper bound. 0 without paid history."
          }
        }
      },
      "BackfillStatus": {
        "type": "object",
        "required": [
          "estimate",
          "backfill"
        ],
        "properties": {
          "estimate": {
            "$ref": "#/components/schemas/BackfillEstimate"
          },
          "backfill": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/SignalBackfill"
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "Settings": {
        "type": "object",
        "required": [
          "auto_backfill_signals"
        ],
        "properties": {
          "auto_backfill_signals": {
            "type": "boolean",
            "description": "Creating a signal automatically backfills existing objects for it. Off by default because each analysis is billed."
          }
        }
      },
      "SettingsUpdate": {
        "type": "object",
        "properties": {
          "auto_backfill_signals": {
            "type": "boolean"
          }
        }
      },
      "WebhookEventType": {
        "type": "string",
        "enum": [
          "analysis.completed",
          "rule.triggered"
        ]
      },
      "WebhookEndpoint": {
        "type": "object",
        "required": [
          "id",
          "url",
          "description",
          "enabled",
          "signal_keys",
          "events",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "description": {
            "type": "string"
          },
          "enabled": {
            "type": "boolean"
          },
          "signal_keys": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Only deliver analyses that answered one of these signals. Empty means every analysis."
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookEventType"
            },
            "description": "The event types this endpoint receives."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "WebhookEndpointCreate": {
        "type": "object",
        "required": [
          "url"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "An `http` or `https` URL.",
            "examples": [
              "https://example.com/hooks/analyze"
            ]
          },
          "description": {
            "type": "string"
          },
          "signal_keys": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "events": {
            "type": "array",
            "minItems": 1,
            "uniqueItems": true,
            "items": {
              "$ref": "#/components/schemas/WebhookEventType"
            },
            "description": "The event types to deliver. Omit to receive every event type. Use\n`[\"rule.triggered\"]` for an endpoint that only receives rule firings.\n"
          },
          "enabled": {
            "type": "boolean",
            "default": true
          }
        }
      },
      "WebhookEndpointUpdate": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "format": "uri"
          },
          "description": {
            "type": "string"
          },
          "signal_keys": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "events": {
            "type": "array",
            "minItems": 1,
            "uniqueItems": true,
            "items": {
              "$ref": "#/components/schemas/WebhookEventType"
            }
          },
          "enabled": {
            "type": "boolean"
          }
        }
      },
      "WebhookEndpointCreated": {
        "type": "object",
        "required": [
          "endpoint",
          "secret",
          "note"
        ],
        "properties": {
          "endpoint": {
            "$ref": "#/components/schemas/WebhookEndpoint"
          },
          "secret": {
            "type": "string",
            "description": "The signing secret. Shown only once."
          },
          "note": {
            "type": "string"
          }
        }
      },
      "WebhookEndpointList": {
        "type": "object",
        "required": [
          "webhooks"
        ],
        "properties": {
          "webhooks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookEndpoint"
            }
          }
        }
      },
      "DeliveryStatus": {
        "type": "string",
        "enum": [
          "pending",
          "delivering",
          "delivered",
          "dead"
        ],
        "description": "`dead` deliveries are in the dead-letter queue: permanently rejected, or out of retries."
      },
      "WebhookDelivery": {
        "type": "object",
        "required": [
          "id",
          "endpoint_id",
          "event_type",
          "status",
          "attempts",
          "max_attempts",
          "run_after",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "endpoint_id": {
            "type": "string",
            "format": "uuid"
          },
          "event_type": {
            "type": "string",
            "enum": [
              "analysis.completed",
              "rule.triggered"
            ]
          },
          "object_id": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/DeliveryStatus"
          },
          "attempts": {
            "type": "integer"
          },
          "max_attempts": {
            "type": "integer"
          },
          "last_status_code": {
            "type": "integer",
            "description": "The endpoint's last HTTP status, if it answered."
          },
          "last_error": {
            "type": "string"
          },
          "run_after": {
            "type": "string",
            "format": "date-time",
            "description": "When the next attempt is due."
          },
          "delivered_at": {
            "type": "string",
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "payload": {
            "type": "object",
            "additionalProperties": true,
            "description": "The body that is (or was) delivered."
          }
        }
      },
      "WebhookDeliveryList": {
        "type": "object",
        "required": [
          "deliveries"
        ],
        "properties": {
          "deliveries": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookDelivery"
            }
          }
        }
      },
      "AnalysisCompletedEvent": {
        "type": "object",
        "required": [
          "event",
          "object_id",
          "tenant_id",
          "model",
          "occurred_at",
          "answers"
        ],
        "properties": {
          "event": {
            "type": "string",
            "const": "analysis.completed"
          },
          "object_id": {
            "type": "string"
          },
          "tenant_id": {
            "type": "string",
            "format": "uuid"
          },
          "model": {
            "type": "string"
          },
          "occurred_at": {
            "type": "string",
            "format": "date-time"
          },
          "answers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Answer"
            }
          }
        }
      },
      "RuleTriggeredEvent": {
        "type": "object",
        "required": [
          "event",
          "rule_id",
          "tenant_id",
          "object_id",
          "matched_when",
          "occurred_at",
          "values"
        ],
        "properties": {
          "event": {
            "type": "string",
            "const": "rule.triggered"
          },
          "rule_id": {
            "type": "string",
            "format": "uuid"
          },
          "rule_name": {
            "type": "string"
          },
          "tenant_id": {
            "type": "string",
            "format": "uuid"
          },
          "object_id": {
            "type": "string"
          },
          "matched_when": {
            "type": "string"
          },
          "model": {
            "type": "string"
          },
          "occurred_at": {
            "type": "string",
            "format": "date-time"
          },
          "values": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Answer"
            }
          }
        }
      },
      "ActionType": {
        "type": "string",
        "enum": [
          "email",
          "slack",
          "webhook"
        ]
      },
      "ActionConfig": {
        "type": "object",
        "additionalProperties": true,
        "description": "Depends on `action_type`:\n\n- `email`: `{\"to\": \"abuse@acme.com\", \"subject\": \"…\"}` (`subject` optional)\n- `slack`: `{\"webhook_url\": \"https://hooks.slack.com/…\"}`\n- `webhook`: `{\"endpoint_id\": \"<registered endpoint id>\"}`\n"
      },
      "Rule": {
        "type": "object",
        "required": [
          "id",
          "name",
          "when",
          "action_type",
          "action_config",
          "enabled",
          "cooldown_seconds",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "when": {
            "type": "string",
            "description": "The condition, e.g. `is_scammer >= 90 and trust_score < 1`."
          },
          "action_type": {
            "$ref": "#/components/schemas/ActionType"
          },
          "action_config": {
            "$ref": "#/components/schemas/ActionConfig"
          },
          "enabled": {
            "type": "boolean"
          },
          "cooldown_seconds": {
            "type": "integer",
            "description": "0 fires only when the condition turns true; more lets the rule re-fire after that long while it stays true."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "RuleCreate": {
        "type": "object",
        "required": [
          "when",
          "action_type",
          "action_config"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "when": {
            "type": "string"
          },
          "action_type": {
            "$ref": "#/components/schemas/ActionType"
          },
          "action_config": {
            "$ref": "#/components/schemas/ActionConfig"
          },
          "enabled": {
            "type": "boolean",
            "default": true
          },
          "cooldown_seconds": {
            "type": "integer",
            "minimum": 0,
            "default": 0
          }
        }
      },
      "RuleUpdate": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "when": {
            "type": "string"
          },
          "action_type": {
            "$ref": "#/components/schemas/ActionType"
          },
          "action_config": {
            "$ref": "#/components/schemas/ActionConfig"
          },
          "enabled": {
            "type": "boolean"
          },
          "cooldown_seconds": {
            "type": "integer",
            "minimum": 0
          }
        }
      },
      "RuleList": {
        "type": "object",
        "required": [
          "rules"
        ],
        "properties": {
          "rules": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Rule"
            }
          }
        }
      },
      "RuleFiring": {
        "type": "object",
        "required": [
          "id",
          "rule_id",
          "object_id",
          "matched_when",
          "action_type",
          "status",
          "fired_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "rule_id": {
            "type": "string",
            "format": "uuid"
          },
          "object_id": {
            "type": "string"
          },
          "matched_when": {
            "type": "string"
          },
          "signal_values": {
            "type": "object",
            "additionalProperties": true,
            "description": "The signal values that satisfied the condition."
          },
          "action_type": {
            "$ref": "#/components/schemas/ActionType"
          },
          "status": {
            "type": "string",
            "enum": [
              "dispatched",
              "error"
            ]
          },
          "detail": {
            "type": "string",
            "description": "The error, or a short success note."
          },
          "fired_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "RuleFiringList": {
        "type": "object",
        "required": [
          "firings"
        ],
        "properties": {
          "firings": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RuleFiring"
            }
          }
        }
      },
      "APIKeyStatus": {
        "type": "string",
        "enum": [
          "active",
          "inactive",
          "revoked"
        ]
      },
      "APIKey": {
        "type": "object",
        "required": [
          "id",
          "key",
          "name",
          "status",
          "has_secret",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "key": {
            "type": "string",
            "description": "The public key ID, sent as `api_key`.",
            "examples": [
              "7Hx2Qp9LmZ"
            ]
          },
          "name": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/APIKeyStatus"
          },
          "has_secret": {
            "type": "boolean",
            "description": "Whether the key can sign requests. Legacy keys can't until rotated."
          },
          "legacy": {
            "type": "boolean",
            "description": "Created before request signing; rotate it to get a signing secret."
          },
          "legacy_preview": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "last_used_at": {
            "type": "string",
            "format": "date-time"
          },
          "legacy_used_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "APIKeyCreate": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "examples": [
              "Production"
            ]
          }
        }
      },
      "APIKeyUpdate": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "inactive"
            ]
          }
        }
      },
      "APIKeySecret": {
        "type": "object",
        "required": [
          "api_key",
          "key",
          "secret",
          "note"
        ],
        "properties": {
          "api_key": {
            "$ref": "#/components/schemas/APIKey"
          },
          "key": {
            "type": "string",
            "description": "The public key ID."
          },
          "secret": {
            "type": "string",
            "description": "The signing secret. Shown only once."
          },
          "note": {
            "type": "string"
          }
        }
      },
      "APIKeyList": {
        "type": "object",
        "required": [
          "api_keys"
        ],
        "properties": {
          "api_keys": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/APIKey"
            }
          }
        }
      },
      "Balance": {
        "type": "object",
        "required": [
          "balance_cents",
          "currency",
          "metering_enabled",
          "enforcement",
          "self_serve_topup",
          "low_balance_threshold_cents"
        ],
        "properties": {
          "balance_cents": {
            "type": "integer"
          },
          "currency": {
            "type": "string",
            "examples": [
              "usd"
            ]
          },
          "metering_enabled": {
            "type": "boolean",
            "description": "Whether analyses are billed on this deployment."
          },
          "enforcement": {
            "type": "string",
            "description": "`block` pauses analyses at an empty balance; `grace` lets it go negative."
          },
          "self_serve_topup": {
            "type": "boolean",
            "description": "Whether card top-ups are available."
          },
          "low_balance_threshold_cents": {
            "type": "integer"
          },
          "topup_min_cents": {
            "type": "integer"
          },
          "topup_max_cents": {
            "type": "integer"
          }
        }
      },
      "LedgerEntry": {
        "type": "object",
        "required": [
          "id",
          "delta_cents",
          "balance_after_cents",
          "delta_micros",
          "balance_after_micros",
          "reason",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "delta_cents": {
            "type": "integer",
            "description": "Negative for a charge, positive for a credit. Sub-cent charges round to 0; see `delta_micros`."
          },
          "balance_after_cents": {
            "type": "integer"
          },
          "delta_micros": {
            "type": "integer",
            "description": "The exact amount in micro-dollars (1e-6 USD)."
          },
          "balance_after_micros": {
            "type": "integer"
          },
          "reason": {
            "type": "string"
          },
          "model": {
            "type": "string"
          },
          "object_id": {
            "type": "string"
          },
          "cost_usd": {
            "type": "number"
          },
          "cost_margin": {
            "type": "number"
          },
          "input_tokens": {
            "type": "integer"
          },
          "output_tokens": {
            "type": "integer"
          },
          "payment_ref": {
            "type": "string",
            "description": "The Stripe Checkout Session that paid for a top-up."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "LedgerPage": {
        "type": "object",
        "required": [
          "entries",
          "next_cursor"
        ],
        "properties": {
          "entries": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LedgerEntry"
            }
          },
          "next_cursor": {
            "type": "string",
            "description": "Pass as `cursor` for the next page. Empty on the last page."
          }
        }
      },
      "CheckoutCreate": {
        "type": "object",
        "required": [
          "amount_cents"
        ],
        "properties": {
          "amount_cents": {
            "type": "integer",
            "description": "Within the deployment's `topup_min_cents`–`topup_max_cents`.",
            "examples": [
              2500
            ]
          }
        }
      },
      "CheckoutSession": {
        "type": "object",
        "required": [
          "id",
          "url"
        ],
        "properties": {
          "id": {
            "type": "string",
            "examples": [
              "cs_test_a1b2c3"
            ]
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Send the customer here to pay."
          }
        }
      },
      "CheckoutStatus": {
        "type": "object",
        "required": [
          "id",
          "status"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "credited"
            ]
          },
          "amount_cents": {
            "type": "integer"
          },
          "balance_after_cents": {
            "type": "integer"
          }
        }
      },
      "Usage": {
        "type": "object",
        "required": [
          "from",
          "to",
          "currency",
          "metering_enabled",
          "balance_cents",
          "burn_rate_micros_per_day",
          "totals",
          "series",
          "by_model",
          "by_source",
          "by_signal"
        ],
        "properties": {
          "from": {
            "type": "string",
            "format": "date"
          },
          "to": {
            "type": "string",
            "format": "date"
          },
          "currency": {
            "type": "string"
          },
          "metering_enabled": {
            "type": "boolean"
          },
          "balance_cents": {
            "type": "integer"
          },
          "burn_rate_micros_per_day": {
            "type": "integer",
            "description": "Average spend per elapsed day of the range, in micro-dollars."
          },
          "runway_days": {
            "type": "number",
            "description": "Balance divided by burn rate. Omitted without spend or balance."
          },
          "totals": {
            "$ref": "#/components/schemas/UsageTotals"
          },
          "series": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/UsageDay"
            }
          },
          "by_model": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/UsageBreakdown"
            }
          },
          "by_source": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/UsageBreakdown"
            }
          },
          "by_signal": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/UsageSignal"
            }
          }
        }
      },
      "UsageTotals": {
        "type": "object",
        "required": [
          "analyses",
          "spend_micros",
          "input_tokens",
          "output_tokens"
        ],
        "properties": {
          "analyses": {
            "type": "integer"
          },
          "spend_micros": {
            "type": "integer"
          },
          "input_tokens": {
            "type": "integer"
          },
          "output_tokens": {
            "type": "integer"
          }
        }
      },
      "UsageDay": {
        "type": "object",
        "required": [
          "date",
          "analyses",
          "async",
          "sync",
          "spend_micros"
        ],
        "properties": {
          "date": {
            "type": "string",
            "format": "date"
          },
          "analyses": {
            "type": "integer"
          },
          "async": {
            "type": "integer"
          },
          "sync": {
            "type": "integer"
          },
          "spend_micros": {
            "type": "integer"
          }
        }
      },
      "UsageBreakdown": {
        "type": "object",
        "required": [
          "key",
          "analyses",
          "spend_micros",
          "input_tokens",
          "output_tokens"
        ],
        "properties": {
          "key": {
            "type": "string"
          },
          "analyses": {
            "type": "integer"
          },
          "spend_micros": {
            "type": "integer"
          },
          "input_tokens": {
            "type": "integer"
          },
          "output_tokens": {
            "type": "integer"
          }
        }
      },
      "UsageSignal": {
        "type": "object",
        "required": [
          "signal",
          "analyses"
        ],
        "properties": {
          "signal": {
            "type": "string"
          },
          "analyses": {
            "type": "integer"
          }
        }
      },
      "Me": {
        "type": "object",
        "required": [
          "auth_type",
          "tenant_id",
          "tenant_name"
        ],
        "properties": {
          "auth_type": {
            "type": "string",
            "enum": [
              "tenant",
              "console"
            ],
            "description": "`tenant` for API keys, `console` for a signed-in user."
          },
          "tenant_id": {
            "type": "string",
            "format": "uuid"
          },
          "tenant_name": {
            "type": "string"
          },
          "user_id": {
            "type": "string",
            "format": "uuid"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "role": {
            "type": "string"
          },
          "onboarded": {
            "type": "boolean"
          }
        }
      },
      "Token": {
        "type": "object",
        "required": [
          "token",
          "expires_at",
          "token_type"
        ],
        "properties": {
          "token": {
            "type": "string"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "token_type": {
            "type": "string",
            "enum": [
              "console",
              "tenant"
            ]
          },
          "new_user": {
            "type": "boolean",
            "description": "Social sign-in only; true when it created the account."
          }
        }
      },
      "LoginRequest": {
        "type": "object",
        "required": [
          "email",
          "password"
        ],
        "properties": {
          "email": {
            "type": "string",
            "format": "email"
          },
          "password": {
            "type": "string",
            "format": "password"
          },
          "code": {
            "type": "string",
            "description": "An authenticator code or a recovery code. Required only when two-factor authentication is on.",
            "example": "492039"
          }
        }
      },
      "RegisterRequest": {
        "type": "object",
        "required": [
          "tenant_name",
          "email",
          "password"
        ],
        "properties": {
          "tenant_name": {
            "type": "string"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "password": {
            "type": "string",
            "format": "password",
            "minLength": 8
          }
        }
      },
      "TokenExchangeRequest": {
        "type": "object",
        "required": [
          "api_key",
          "secret"
        ],
        "properties": {
          "api_key": {
            "type": "string"
          },
          "secret": {
            "type": "string"
          }
        }
      },
      "ForgotPasswordRequest": {
        "type": "object",
        "required": [
          "email"
        ],
        "properties": {
          "email": {
            "type": "string",
            "format": "email"
          }
        }
      },
      "ResetPasswordRequest": {
        "type": "object",
        "required": [
          "token",
          "password"
        ],
        "properties": {
          "token": {
            "type": "string"
          },
          "password": {
            "type": "string",
            "format": "password",
            "minLength": 8
          }
        }
      },
      "MfaStatus": {
        "type": "object",
        "required": [
          "enabled",
          "recovery_codes_remaining"
        ],
        "properties": {
          "enabled": {
            "type": "boolean"
          },
          "recovery_codes_remaining": {
            "type": "integer",
            "description": "Unused recovery codes; 0 while two-factor is off."
          }
        }
      },
      "MfaSetup": {
        "type": "object",
        "required": [
          "secret",
          "otpauth_uri"
        ],
        "properties": {
          "secret": {
            "type": "string",
            "description": "The shared secret in base32, for typing into an authenticator.",
            "example": "JBSWY3DPEHPK3PXP"
          },
          "otpauth_uri": {
            "type": "string",
            "description": "The same secret as an `otpauth://` URI, to render as a QR code."
          }
        }
      },
      "MfaEnableRequest": {
        "type": "object",
        "required": [
          "code"
        ],
        "properties": {
          "code": {
            "type": "string",
            "description": "A current code from the authenticator.",
            "example": "492039"
          }
        }
      },
      "MfaRecoveryCodes": {
        "type": "object",
        "required": [
          "recovery_codes"
        ],
        "properties": {
          "recovery_codes": {
            "type": "array",
            "description": "Shown once. Store them somewhere safe.",
            "items": {
              "type": "string",
              "example": "K7M2Q-9XW4B"
            }
          }
        }
      },
      "MfaDisableRequest": {
        "type": "object",
        "required": [
          "code"
        ],
        "properties": {
          "password": {
            "type": "string",
            "format": "password",
            "description": "Required unless the account has no password (social sign-in only)."
          },
          "code": {
            "type": "string",
            "description": "A current authenticator code or an unused recovery code."
          }
        }
      },
      "ChangePasswordRequest": {
        "type": "object",
        "required": [
          "current_password",
          "new_password"
        ],
        "properties": {
          "current_password": {
            "type": "string",
            "format": "password"
          },
          "new_password": {
            "type": "string",
            "format": "password",
            "minLength": 8
          }
        }
      },
      "OAuthProviders": {
        "type": "object",
        "required": [
          "providers",
          "allow_registration"
        ],
        "properties": {
          "providers": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "google",
                "github"
              ]
            }
          },
          "allow_registration": {
            "type": "boolean"
          }
        }
      },
      "OAuthAuthorizeRequest": {
        "type": "object",
        "required": [
          "state",
          "code_challenge"
        ],
        "properties": {
          "state": {
            "type": "string",
            "minLength": 32
          },
          "code_challenge": {
            "type": "string",
            "minLength": 43,
            "maxLength": 43,
            "description": "Base64url SHA-256 of the PKCE verifier (S256)."
          }
        }
      },
      "AuthorizationURL": {
        "type": "object",
        "required": [
          "url"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "OAuthCodeRequest": {
        "type": "object",
        "required": [
          "code",
          "code_verifier"
        ],
        "properties": {
          "code": {
            "type": "string"
          },
          "code_verifier": {
            "type": "string"
          },
          "tenant_name": {
            "type": "string",
            "description": "Used only when the sign-in creates a new account."
          }
        }
      },
      "Identity": {
        "type": "object",
        "required": [
          "provider",
          "email",
          "created_at"
        ],
        "properties": {
          "provider": {
            "type": "string",
            "enum": [
              "google",
              "github"
            ]
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Identities": {
        "type": "object",
        "required": [
          "has_password",
          "identities",
          "providers"
        ],
        "properties": {
          "has_password": {
            "type": "boolean"
          },
          "identities": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Identity"
            }
          },
          "providers": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "google",
                "github"
              ]
            }
          }
        }
      },
      "NotificationPreference": {
        "type": "object",
        "required": [
          "event",
          "label",
          "description",
          "enabled",
          "critical"
        ],
        "properties": {
          "event": {
            "type": "string",
            "examples": [
              "low_balance"
            ]
          },
          "label": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "enabled": {
            "type": "boolean"
          },
          "critical": {
            "type": "boolean",
            "description": "Always sent; can't be turned off."
          }
        }
      },
      "NotificationPreferences": {
        "type": "object",
        "required": [
          "preferences"
        ],
        "properties": {
          "preferences": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/NotificationPreference"
            }
          }
        }
      },
      "NotificationPreferencesUpdate": {
        "type": "object",
        "additionalProperties": {
          "type": "boolean"
        },
        "examples": [
          {
            "low_balance": false
          }
        ]
      }
    }
  }
}
