{"components":{"schemas":{"AccountDataErasureStatus":{"description":"Sprint 14i (R1W2 audit fix): tenant_id whitelist validator. Matches\n`^[a-zA-Z0-9_-]{1,64}$`. Prevents newline/quote injection в JSONL logs,\npath-traversal в admin/keys/:tenant_id, и cross-tenant impersonation via\ncrafted IDs. 64 chars matches practical UUID-with-prefix usage.","properties":{"rlm_erased":{"description":"RLM Memory Layer data is gone, or the RLM plane is not configured.","type":"boolean"},"soc_erased":{"description":"`Some(true)` when the SOC plane is configured and confirms erasure.\n`None` means no separate SOC erasure client is configured.","type":["boolean","null"]}},"required":["rlm_erased"],"type":"object"},"AccountSelfTerminateResponse":{"description":"Response к `DELETE /v1/admin/account`.\n\nSprint 14g (audit C3): includes `channel_disclosed` per Принцип 12.","properties":{"channel_disclosed":{"$ref":"#/components/schemas/ChannelDisclosed","description":"Sprint 14g: Принцип 12 invariant."},"data_erasure":{"$ref":"#/components/schemas/AccountDataErasureStatus","description":"Cross-plane customer data erasure completed before account closure."},"data_export_note":{"description":"Reminder of customer-data rights per TOS §7.1.","type":"string"},"refund_note":{"description":"Reminder of refund timing per TOS §7.1.","type":"string"},"revoked_keys":{"description":"Number of API keys revoked (all of them — soft-deleted, audit trail\npreserved per Constitution §6).","minimum":0,"type":"integer"},"revoked_sessions":{"description":"Review 13.06: browser sessions revoked alongside the keys — without\nthis a self-terminated account kept dashboard access through its\nstill-live cookie for up to 7 days.","minimum":0,"type":"integer"},"stripe_subscription_cancelled":{"description":"True when a recorded Stripe subscription was synchronously cancelled\nbefore the account was closed.","type":"boolean"},"tenant_id":{"description":"The terminated tenant identifier.","type":"string"},"terminated_at_utc":{"description":"Acknowledgement timestamp.","type":"string"}},"required":["tenant_id","revoked_keys","revoked_sessions","stripe_subscription_cancelled","data_erasure","terminated_at_utc","data_export_note","refund_note","channel_disclosed"],"type":"object"},"ChannelClass":{"description":"The four upstream relationship classes that Spectorn supports.\n\nSerialised in snake_case via `#[serde(rename_all = \"snake_case\")]` so the\nwire shape exactly matches Constitution §12's published taxonomy.","enum":["track1_security_only","track2_chinese_licensed","track3_western_concierge","track4_openweights_selfhost"],"type":"string"},"ChannelDisclosed":{"description":"The Constitution §12 disclosure payload — embedded in every response.\n\nFor Track 1 (security-only) most fields are `None`; only `channel_class`\nis meaningful. For Track 2 / Track 4, `model`, `upstream_provider`, and\n`license_or_agreement_ref` are populated. Track 3 additionally populates\n`subscription_owner` and `risk_ack_id`.\n\n**Invariant**: callers that build a response MUST construct a\n`ChannelDisclosed`; there is no `Default` impl that would let a handler\nsilently emit a malformed disclosure. Use the constructors below.","properties":{"channel_class":{"$ref":"#/components/schemas/ChannelClass","description":"One of the four canonical channel classes."},"license_or_agreement_ref":{"description":"Reference verifying the channel claim:\n- Track 2: reseller agreement ID (e.g. \"ACI-RA-2026-SPCT-001\")\n- Track 3: signed risk-acknowledgment UUID\n- Track 4: open-weight license name (e.g. \"Apache-2.0\",\n  \"Llama-3.3-Community-License\")\n- Track 1: `None` (no upstream)","type":["string","null"]},"model":{"description":"Exact upstream model name (e.g. `qwen3-coder-plus`, `claude-4.6-sonnet`).\n`None` for Track 1 (no model invoked).","type":["string","null"]},"risk_ack_id":{"description":"Track 3 only — UUID of customer's signed Risk Acknowledgment.\nMust match a record in the immutable risk-ack store.","type":["string","null"]},"subscription_owner":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/SubscriptionOwner","description":"Track 3 only — subscription legal ownership."}]},"upstream_provider":{"description":"Legal entity name of upstream provider (e.g. \"Alibaba Cloud International\",\n\"Anthropic, PBC\", \"Self-hosted on Hetzner Cloud GmbH\").\n`None` for Track 1.","type":["string","null"]}},"required":["channel_class"],"type":"object"},"DisputeStatus":{"description":"Lifecycle states of a dispute.","enum":["received","under_review","resolved","refunded","escalated"],"type":"string"},"EnginesResponse":{"properties":{"channel_disclosed":{"$ref":"#/components/schemas/ChannelDisclosed","description":"Sprint 14g: Принцип 12 invariant."},"engines":{"items":{"type":"string"},"type":"array"},"total":{"minimum":0,"type":"integer"}},"required":["total","engines","channel_disclosed"],"type":"object"},"FileDisputeRequest":{"description":"Customer-supplied request body for `POST /v1/dispute/file`.","properties":{"body":{"type":"string"},"principle_reference":{"type":["string","null"]},"subject":{"type":"string"}},"required":["subject","body"],"type":"object"},"FileDisputeResponse":{"description":"Response к customer after filing.\n\nSprint 14g (audit C3): includes `channel_disclosed` per Принцип 12.","properties":{"channel_disclosed":{"$ref":"#/components/schemas/ChannelDisclosed","description":"Sprint 14g: Принцип 12 invariant. Dispute filing — no LLM upstream,\nhence Track 1."},"dispute_id":{"type":"string"},"expected_response_within_business_days":{"format":"int32","minimum":0,"type":"integer"},"note":{"type":"string"},"received_at_utc":{"type":"string"},"status":{"$ref":"#/components/schemas/DisputeStatus"}},"required":["dispute_id","status","received_at_utc","expected_response_within_business_days","note","channel_disclosed"],"type":"object"},"HealthResponse":{"properties":{"build_dirty":{"type":"boolean"},"channel_disclosed":{"$ref":"#/components/schemas/ChannelDisclosed","description":"Sprint 14g: Принцип 12 invariant. Liveness probe — security-only Track 1."},"engine_count":{"minimum":0,"type":"integer"},"status":{"type":"string"},"vcs_ref":{"type":"string"}},"required":["status","engine_count","vcs_ref","build_dirty","channel_disclosed"],"type":"object"},"SubscriptionOwner":{"description":"Who legally owns the Track 3 subscription. Disclosed per TOS §11.1.\n\n* `ClientDedicated` — subscription in customer's own name; cleanest юр.форма.\n* `SpectornManaged` — subscription in Spectorn Concierge SP's name on\n  behalf of customer (used when customer cannot register directly due to\n  geographic restrictions).","enum":["client_dedicated","spectorn_managed"],"type":"string"}},"securitySchemes":{"bearerAuth":{"bearerFormat":"Spectorn API key","scheme":"bearer","type":"http"},"cookieAuth":{"in":"cookie","name":"spectorn_session","type":"apiKey"}}},"info":{"contact":{"email":"ops@spectorn.xyz","name":"Spectorn AI Embassy LLP"},"description":"Spectorn — AI security verdict service (Track 1) + hybrid LLM gateway (Tracks 2/3/4 launching Sprint 15-17). Constitution v0.2 (12 falsifiable principles, two non-amendable). Per Constitution Принцип 12, every response includes a `channel_disclosed` field; responses can be independently verified using the open-source `spct-verify-channel` CLI. See SPECTORN_CONSTITUTION_DRAFT.md and SPECTORN_TOS_DRAFT.md в the source repository.","license":{"name":"Apache-2.0 (server) / proprietary (engine library)"},"title":"Spectorn AI Security Gateway","version":"0.1.0-sprint14"},"openapi":"3.1.0","paths":{"/v1/admin/account":{"delete":{"description":"**Distinct from `/v1/admin/keys/:tenant_id`**: that endpoint requires\nEnterprise tier and acts on a different tenant (admin revoking a\ncustomer). This endpoint acts on the caller's OWN account и does NOT\nrequire Enterprise tier — any tier may terminate their own subscription.\n\nEffect:\n  1. ALL of the caller's API keys are revoked (soft delete, `active=false`)\n  2. Future auth attempts with these keys return 401\n  3. Usage log entry recorded для audit trail\n  4. Response includes pointers к data export (manual process Sprint 14;\n     automated в Sprint 16) и refund timing per TOS §7.1\n\nIdempotent: calling twice on already-terminated account returns 200 с\n`revoked_keys: 0` (no error — termination is the goal state).","operationId":"account_self_terminate","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountSelfTerminateResponse"}}},"description":"Account terminated; all keys revoked; data export instructions returned"},"401":{"description":"Missing или invalid API key"}},"security":[{"api_key":[]}],"summary":"`DELETE /v1/admin/account` — customer self-termination per TOS §7.1.","tags":["account"]}},"/v1/admin/detection-tuning":{"get":{"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Bad request"},"401":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Missing or invalid API key"},"429":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Rate limit exceeded"},"500":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Internal error"}},"security":[{"bearerAuth":[]},{"cookieAuth":[]}],"summary":"Read the live global detection tuning baseline","tags":["account"]},"put":{"requestBody":{"content":{"application/json":{"schema":{"properties":{"policy":{"nullable":true,"properties":{"allow_list":{"items":{"type":"string"},"type":"array"},"block_threshold":{"maximum":1.0,"minimum":0.0,"type":"number"},"category_multipliers":{"additionalProperties":{"minimum":0.0,"type":"number"},"type":"object"},"deny_list":{"items":{"type":"string"},"type":"array"}},"type":"object"}},"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Bad request"},"401":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Missing or invalid API key"},"429":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Rate limit exceeded"},"500":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Internal error"}},"security":[{"bearerAuth":[]},{"cookieAuth":[]}],"summary":"Set or clear the live global detection tuning baseline","tags":["account"]}},"/v1/admin/detection-tuning/reload":{"post":{"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Bad request"},"401":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Missing or invalid API key"},"429":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Rate limit exceeded"},"500":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Internal error"}},"security":[{"bearerAuth":[]},{"cookieAuth":[]}],"summary":"Reload the global detection tuning baseline from disk","tags":["account"]}},"/v1/admin/keys":{"get":{"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Bad request"},"401":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Missing or invalid API key"},"429":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Rate limit exceeded"},"500":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Internal error"}},"security":[{"bearerAuth":[]},{"cookieAuth":[]}],"summary":"List API keys","tags":["account"]},"post":{"requestBody":{"content":{"application/json":{"schema":{"properties":{"label":{"type":"string"},"rate_limit_per_min":{"minimum":1,"type":"integer"},"tenant_id":{"type":"string"},"tier":{"type":"string"}},"required":["tenant_id","tier"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Bad request"},"401":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Missing or invalid API key"},"429":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Rate limit exceeded"},"500":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Internal error"}},"security":[{"bearerAuth":[]},{"cookieAuth":[]}],"summary":"Create an API key","tags":["account"]}},"/v1/admin/keys/{tenant_id}":{"delete":{"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Bad request"},"401":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Missing or invalid API key"},"429":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Rate limit exceeded"},"500":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Internal error"}},"security":[{"bearerAuth":[]},{"cookieAuth":[]}],"summary":"Revoke all keys for a tenant","tags":["account"]}},"/v1/admin/tenants/{tenant_id}/model-routes":{"get":{"parameters":[{"description":"Target tenant id","in":"path","name":"tenant_id","required":true,"schema":{"pattern":"^[a-zA-Z0-9_-]{1,64}$","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Bad request"},"401":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Missing or invalid API key"},"429":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Rate limit exceeded"},"500":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Internal error"}},"security":[{"bearerAuth":[]},{"cookieAuth":[]}],"summary":"Read a tenant's gateway model aliases","tags":["account"]},"put":{"parameters":[{"description":"Target tenant id","in":"path","name":"tenant_id","required":true,"schema":{"pattern":"^[a-zA-Z0-9_-]{1,64}$","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"properties":{"routes":{"items":{"properties":{"display_name":{"maxLength":120,"type":"string"},"enabled":{"default":true,"type":"boolean"},"id":{"pattern":"^[A-Za-z0-9._-]{1,96}$","type":"string"},"provider":{"pattern":"^[A-Za-z0-9_-]+$","type":"string"},"upstream_model":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id","provider","upstream_model"],"type":"object"},"maxItems":256,"type":"array"}},"required":["routes"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Bad request"},"401":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Missing or invalid API key"},"429":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Rate limit exceeded"},"500":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Internal error"}},"security":[{"bearerAuth":[]},{"cookieAuth":[]}],"summary":"Replace a tenant's gateway model aliases","tags":["account"]}},"/v1/admin/tenants/{tenant_id}/policy":{"put":{"parameters":[{"description":"Target tenant id","in":"path","name":"tenant_id","required":true,"schema":{"pattern":"^[a-zA-Z0-9_-]{1,64}$","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"nullable":true,"properties":{"allow_list":{"items":{"type":"string"},"type":"array"},"block_threshold":{"maximum":1.0,"minimum":0.0,"type":"number"},"category_multipliers":{"additionalProperties":{"minimum":0.0,"type":"number"},"type":"object"},"deny_list":{"items":{"type":"string"},"type":"array"}},"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Bad request"},"401":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Missing or invalid API key"},"429":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Rate limit exceeded"},"500":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Internal error"}},"security":[{"bearerAuth":[]},{"cookieAuth":[]}],"summary":"Set or clear a tenant's policy (null body clears to default)","tags":["account"]}},"/v1/admin/tenants/{tenant_id}/scan-config":{"get":{"parameters":[{"description":"Target tenant id","in":"path","name":"tenant_id","required":true,"schema":{"pattern":"^[a-zA-Z0-9_-]{1,64}$","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Bad request"},"401":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Missing or invalid API key"},"429":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Rate limit exceeded"},"500":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Internal error"}},"security":[{"bearerAuth":[]},{"cookieAuth":[]}],"summary":"Read a tenant's scan mode + policy","tags":["account"]}},"/v1/admin/tenants/{tenant_id}/scan-mode":{"put":{"parameters":[{"description":"Target tenant id","in":"path","name":"tenant_id","required":true,"schema":{"pattern":"^[a-zA-Z0-9_-]{1,64}$","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"properties":{"mode":{"enum":["enforce","shadow","observe"],"type":"string"}},"required":["mode"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Bad request"},"401":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Missing or invalid API key"},"429":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Rate limit exceeded"},"500":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Internal error"}},"security":[{"bearerAuth":[]},{"cookieAuth":[]}],"summary":"Set a tenant's scan enforcement mode (enforce/shadow)","tags":["account"]}},"/v1/audit/verify":{"get":{"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Bad request"},"401":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Missing or invalid API key"},"429":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Rate limit exceeded"},"500":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Internal error"}},"security":[{"bearerAuth":[]},{"cookieAuth":[]}],"summary":"Verify the audit log","tags":["verification"]}},"/v1/billing/checkout":{"post":{"requestBody":{"content":{"application/json":{"schema":{"properties":{"target_tier":{"enum":["Basic","Pro"],"type":"string"}},"required":["target_tier"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Bad request"},"401":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Missing or invalid API key"},"429":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Rate limit exceeded"},"500":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Internal error"}},"security":[{"bearerAuth":[]},{"cookieAuth":[]}],"summary":"Create a Stripe Checkout session","tags":["account"]}},"/v1/chat/completions":{"post":{"parameters":[{"description":"BYOK upstream provider key when no provider is configured in the dashboard","in":"header","name":"X-Provider-Key","required":false,"schema":{"type":"string"}},{"description":"Route to a configured provider without prefixing the model name","in":"header","name":"X-Provider","required":false,"schema":{"type":"string"}},{"description":"Opt in to gateway memory recall/store for this conversation","in":"header","name":"X-Spectorn-Session","required":false,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"properties":{"messages":{"items":{"additionalProperties":true,"type":"object"},"type":"array"},"model":{"description":"Model id, optionally prefixed with provider/ for routing","type":"string"},"stream":{"default":false,"type":"boolean"}},"required":["model","messages"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Bad request"},"401":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Missing or invalid API key"},"429":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Rate limit exceeded"},"500":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Internal error"}},"security":[{"bearerAuth":[]},{"cookieAuth":[]}],"summary":"OpenAI-compatible gateway proxy: scan, route, recall memory, then forward to the model","tags":["gateway"]}},"/v1/dashboard-template":{"get":{"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Bad request"},"500":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Internal error"}},"summary":"Grafana dashboard template","tags":["verification"]}},"/v1/dispute/file":{"post":{"description":"Authenticated: caller's `tenant_id` is extracted from API key and stamped\nonto the dispute record (cannot be spoofed; cross-tenant defence applies).\n\nBody (JSON):\n```json\n{\n  \"subject\": \"string, one-line summary\",\n  \"body\": \"string, full text (markdown OK)\",\n  \"principle_reference\": \"string (optional, e.g. 'Принцип 6')\"\n}\n```\n\nResponse (HTTP 202 Accepted):\n```json\n{\n  \"dispute_id\": \"D-<16-hex>\",\n  \"status\": \"received\",\n  \"received_at_utc\": \"...\",\n  \"expected_response_within_business_days\": 5,\n  \"note\": \"ops staff will respond out-of-band by email; check tenant email\"\n}\n```\n\nStorage: append-only `data/disputes.jsonl`. Sprint 16 will add automated\nforwarding (Telegram/email webhooks) — until then ops manually monitors\nthe file и responds out-of-band.\n\nCost: 1 token (filed under filing_dispute usage label).","operationId":"dispute_file","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FileDisputeRequest"}}},"required":true},"responses":{"202":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FileDisputeResponse"}}},"description":"Dispute filed, in-memory ID assigned, ops staff notified out-of-band"},"400":{"description":"Malformed request (empty subject/body или oversized)"},"401":{"description":"Missing или invalid API key"},"500":{"description":"Internal error (failed к persist dispute)"}},"security":[{"api_key":[]}],"summary":"POST /v1/dispute/file — file a customer dispute per Constitution §6 +\nTOS §7.3.","tags":["account"]}},"/v1/dispute/list":{"get":{"description":"Reads the append-only `data/disputes.jsonl` and returns only records whose\n`tenant_id` matches the caller (cross-tenant isolation enforced inside\n`list_by_tenant`). Complements `POST /v1/dispute/file` so a customer can\nreplay the full dispute history they filed (Constitution §6 audit trail) —\npreviously disputes could be written but never read back.","operationId":"dispute_list","responses":{"200":{"description":"The caller's own disputes in append (chronological) order"},"401":{"description":"Missing or invalid API key"},"500":{"description":"Internal error (failed to read dispute log)"}},"security":[{"api_key":[]}],"summary":"GET /v1/dispute/list — list the authenticated tenant's OWN disputes.","tags":["account"]}},"/v1/engines":{"get":{"operationId":"engines","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnginesResponse"}}},"description":"Full canonical list of available engines"},"401":{"description":"Missing или invalid API key"}},"security":[{"api_key":[]}],"summary":"GET /v1/engines — return the canonical 101 engine names.","tags":["scan"]}},"/v1/explain/{verdict_id}":{"get":{"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Bad request"},"401":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Missing or invalid API key"},"429":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Rate limit exceeded"},"500":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Internal error"}},"security":[{"bearerAuth":[]},{"cookieAuth":[]}],"summary":"Fetch a causal chain for a verdict","tags":["verification"]}},"/v1/gateway/capabilities":{"get":{"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Bad request"},"401":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Missing or invalid API key"},"429":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Rate limit exceeded"},"500":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Internal error"}},"security":[{"bearerAuth":[]},{"cookieAuth":[]}],"summary":"Return tenant gateway capabilities and available model aliases","tags":["gateway"]}},"/v1/health":{"get":{"operationId":"health","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthResponse"}}},"description":"Server is alive (process responding)"}},"tags":["verification"]}},"/v1/logout":{"post":{"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Bad request"},"500":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Internal error"}},"summary":"Logout the current browser session","tags":["account"]}},"/v1/memory/all":{"delete":{"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Bad request"},"401":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Missing or invalid API key"},"429":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Rate limit exceeded"},"500":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Internal error"}},"security":[{"bearerAuth":[]},{"cookieAuth":[]}],"summary":"Delete all tenant memory","tags":["memory"]}},"/v1/memory/export":{"post":{"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Bad request"},"401":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Missing or invalid API key"},"429":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Rate limit exceeded"},"500":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Internal error"}},"security":[{"bearerAuth":[]},{"cookieAuth":[]}],"summary":"Export tenant memory","tags":["memory"]}},"/v1/memory/search":{"get":{"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Bad request"},"401":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Missing or invalid API key"},"429":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Rate limit exceeded"},"500":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Internal error"}},"security":[{"bearerAuth":[]},{"cookieAuth":[]}],"summary":"Search tenant memory","tags":["memory"]}},"/v1/memory/store":{"post":{"requestBody":{"content":{"application/json":{"schema":{"properties":{"content":{"type":"string"},"domain":{"type":"string"},"level":{"default":1,"maximum":3,"minimum":0,"type":"integer"},"module":{"type":"string"}},"required":["content"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Bad request"},"401":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Missing or invalid API key"},"429":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Rate limit exceeded"},"500":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Internal error"}},"security":[{"bearerAuth":[]},{"cookieAuth":[]}],"summary":"Store a tenant memory fact","tags":["memory"]}},"/v1/metrics":{"get":{"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Bad request"},"500":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Internal error"}},"summary":"Prometheus metrics","tags":["verification"]}},"/v1/model-routes":{"get":{"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Bad request"},"401":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Missing or invalid API key"},"429":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Rate limit exceeded"},"500":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Internal error"}},"security":[{"bearerAuth":[]},{"cookieAuth":[]}],"summary":"Read the caller tenant's gateway model aliases","tags":["gateway"]},"put":{"requestBody":{"content":{"application/json":{"schema":{"properties":{"routes":{"items":{"properties":{"display_name":{"maxLength":120,"type":"string"},"enabled":{"default":true,"type":"boolean"},"id":{"pattern":"^[A-Za-z0-9._-]{1,96}$","type":"string"},"provider":{"pattern":"^[A-Za-z0-9_-]+$","type":"string"},"upstream_model":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id","provider","upstream_model"],"type":"object"},"maxItems":256,"type":"array"}},"required":["routes"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Bad request"},"401":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Missing or invalid API key"},"429":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Rate limit exceeded"},"500":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Internal error"}},"security":[{"bearerAuth":[]},{"cookieAuth":[]}],"summary":"Replace the caller tenant's gateway model aliases","tags":["gateway"]}},"/v1/models":{"get":{"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Bad request"},"401":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Missing or invalid API key"},"429":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Rate limit exceeded"},"500":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Internal error"}},"security":[{"bearerAuth":[]},{"cookieAuth":[]}],"summary":"List customer-visible gateway model aliases","tags":["gateway"]}},"/v1/observe":{"post":{"requestBody":{"content":{"application/json":{"schema":{"properties":{"engine_name":{"type":"string"},"observation":{"additionalProperties":true,"type":"object"}},"required":["engine_name","observation"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Bad request"},"401":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Missing or invalid API key"},"429":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Rate limit exceeded"},"500":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Internal error"}},"security":[{"bearerAuth":[]},{"cookieAuth":[]}],"summary":"Submit a structured observation to a stateful engine","tags":["scan"]}},"/v1/public/channel-matrix":{"get":{"description":"Returns the full Spectorn model catalog with channel classes, upstream\nproviders, license/agreement references, and availability status, wrapped\nin the review-13.06 honest envelope (`attestation::build_envelope`):\ned25519-SIGNED when the operator configured\n`SPECTORN_ATTESTATION_SIGNING_KEY`, otherwise an integrity CHECKSUM\nlabelled as exactly that (a bare hash proves nothing about tampering —\nthe old envelope's \"untampered in transit\" claim was false and violated\nПринцип 4).\n\nLookup order для the underlying matrix:\n  1. `data/channel_matrix.json` (relative to server working directory)\n  2. `EMBEDDED_CHANNEL_MATRIX` (compile-time default)\n\n**Unauthenticated** — this is a public attestation endpoint per\nConstitution §12 verification method. The same JSON is shown to\nauditors, customers, and search engines.","operationId":"public_channel_matrix","responses":{"200":{"description":"Channel-matrix envelope: ed25519-signed when the server has an attestation key, blake3 integrity-checksum otherwise"},"500":{"description":"Internal error (malformed on-disk matrix)"}},"summary":"GET /v1/public/channel-matrix — Constitution Принцип 12 verification.","tags":["constitution"]}},"/v1/public/firewall-attestation":{"get":{"description":"Returns the latest output of `infra/scripts/verify_firewall.ps1` wrapped\nin a signed envelope. Operators are expected to refresh the underlying\n`data/firewall_attestation.json` via a cron job (typical cadence:\nhourly). The handler does NOT shell out to the verifier itself — that\nwould be a security risk (arbitrary script execution per request) and a\nperformance hit. Instead the cron produces a fresh attestation file и\nthis endpoint serves whatever is on disk.\n\n**Staleness handling**: the envelope includes both `data.last_run_utc`\n(from the verifier) and `served_at` (from this handler). Customers и\nauditors compute the delta and decide whether the attestation is fresh\nenough for their use. Default staleness threshold per Constitution §11\nbreach-protocol guidance: > 24 hours = treat as `unknown_state`.\n\n**404 vs 500**: if the file doesn't exist yet (cron hasn't run), return\n503 with a body explaining why — это more truthful than a fabricated\n\"pass\" response that would be a Принцип 12 / Принцип 4 violation.\n\n**Unauthenticated** — public verification endpoint per Constitution §11.","operationId":"public_firewall_attestation","responses":{"200":{"description":"Signed firewall-attestation envelope с freshness label"},"500":{"description":"Internal error (malformed attestation file)"},"503":{"description":"Attestation file not yet produced — operator must run verify_firewall.ps1 -OutputJson cron"}},"summary":"GET /v1/public/firewall-attestation — Constitution Принцип 11 verification.","tags":["constitution"]}},"/v1/scan":{"post":{"requestBody":{"content":{"application/json":{"schema":{"properties":{"conversation_id":{"type":"string"},"engine_name":{"default":"injection","description":"Use all for fan-out scan","type":"string"},"options":{"additionalProperties":true,"type":"object"},"prompt":{"type":"string"}},"required":["prompt"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Bad request"},"401":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Missing or invalid API key"},"429":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Rate limit exceeded"},"500":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Internal error"}},"security":[{"bearerAuth":[]},{"cookieAuth":[]}],"summary":"Analyze a prompt with one engine or the full registry","tags":["scan"]}},"/v1/scan/artifact":{"post":{"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Bad request"},"401":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Missing or invalid API key"},"429":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Rate limit exceeded"},"500":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Internal error"}},"security":[{"bearerAuth":[]},{"cookieAuth":[]}],"summary":"Scan a model artifact byte stream","tags":["scan"]}},"/v1/security/self-defense":{"get":{"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Bad request"},"401":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Missing or invalid API key"},"429":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Rate limit exceeded"},"500":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Internal error"}},"security":[{"bearerAuth":[]},{"cookieAuth":[]}],"summary":"Return runtime self-defense mode and recent security events","tags":["security"]}},"/v1/security/self-defense/recover":{"post":{"requestBody":{"content":{"application/json":{"schema":{"properties":{"reason":{"maxLength":512,"minLength":1,"type":"string"},"target_mode":{"default":"normal","enum":["normal","suspicious","degraded_safe","containment","forensic_lockdown"],"type":"string"}},"required":["reason"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Bad request"},"401":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Missing or invalid API key"},"429":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Rate limit exceeded"},"500":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Internal error"}},"security":[{"bearerAuth":[]},{"cookieAuth":[]}],"summary":"Recover runtime self-defense mode after operator review","tags":["security"]}},"/v1/signup":{"post":{"requestBody":{"content":{"application/json":{"schema":{"properties":{"email":{"format":"email","type":"string"}},"required":["email"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Bad request"},"500":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Internal error"}},"summary":"Request a signup magic link","tags":["account"]}},"/v1/signup/verify":{"get":{"parameters":[{"description":"One-time signup magic-link token","in":"query","name":"token","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Bad request"},"500":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Internal error"}},"summary":"Render signup magic-link confirmation","tags":["account"]},"post":{"parameters":[{"description":"One-time signup magic-link token","in":"query","name":"token","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Bad request"},"500":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Internal error"}},"summary":"Consume a signup magic-link token","tags":["account"]}},"/v1/usage":{"get":{"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Bad request"},"401":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Missing or invalid API key"},"429":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Rate limit exceeded"},"500":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Internal error"}},"security":[{"bearerAuth":[]},{"cookieAuth":[]}],"summary":"Return current tenant usage summary","tags":["account"]}},"/v1/webhooks/stripe":{"post":{"parameters":[{"description":"Stripe webhook signature header","in":"header","name":"Stripe-Signature","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Bad request"},"500":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Internal error"}},"summary":"Receive Stripe webhook events","tags":["billing"]}}},"servers":[{"description":"Production","url":"https://spectorn.xyz"},{"description":"Local development","url":"http://localhost:8080"}],"tags":[{"description":"Constitution §11/§12 public verification endpoints; unauthenticated","name":"verification"},{"description":"Detection scan endpoints (Track 1 — security verdict service)","name":"scan"},{"description":"Customer account lifecycle (per TOS §7.1 self-termination, §7.3 disputes)","name":"account"},{"description":"Constitution v0.2 protocol surfaces — non-amendable принципы 11, 12","name":"constitution"}]}